Display CMS Optimierungen 29-05-2026
- Mediathek: Video-Vorschaubilder statt Icons (FFmpeg-Thumbnails + Backfill-Command), Kategorie "Sonstiges" - B2in Media-Picker zeigt alle Medientypen, Typ wird automatisch erkannt; Thumbnail-Preview vor allen Medien-URL-Feldern - B2in Marke/Footer: Footer ein/aus, Logo+Claim frei positionierbar (Ecken) mit Constraints, separate Anzeige-Schalter - Angebote-Modul dynamisch: kein Slide-Typ mehr, einheitliches Detail-Layout mit ein-/ausblendbaren Bloecken, Logo/Brand pro Slide, Streichpreis-Option - Player: leere Module stoppen Endlosschleife, dynamische Layout-Anpassung bei verstecktem Footer/Header - Fix: Script-Ladereihenfolge (Livewire vor Flux), entfernte stale public/flux/flux.js, Modal-Crash beim Aktualisieren behoben Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9262132325
commit
6c6d683b9a
42 changed files with 2267 additions and 13905 deletions
|
|
@ -119,6 +119,7 @@
|
|||
|
||||
{{ $slot }}
|
||||
|
||||
@livewireScripts
|
||||
@fluxScripts
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -380,9 +380,9 @@
|
|||
|
||||
<flux:toast />
|
||||
|
||||
{{-- Flux vor Livewire: flux.js registriert Alpine.data('fluxModal') im alpine:init-Handler --}}
|
||||
@fluxScripts
|
||||
{{-- Reihenfolge laut Flux-Doku: Livewire stellt das gebündelte Alpine bereit, danach registriert flux.js seine Alpine-Komponenten (fluxModal, …). --}}
|
||||
@livewireScripts
|
||||
@fluxScripts
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -26,17 +26,5 @@
|
|||
@livewireScripts
|
||||
@fluxScripts
|
||||
@include('partials.theme-toggle-script')
|
||||
<script src="{{ asset('vendor/livewire/livewire.js') }}"></script>
|
||||
|
||||
<!-- Debug: Script-Status -->
|
||||
<script>
|
||||
console.log('Body Scripts geladen');
|
||||
console.log('Livewire JS:', {{ file_exists(public_path('vendor/livewire/livewire.js')) ? 'true' : 'false' }});
|
||||
if (typeof Livewire !== 'undefined') {
|
||||
console.log('Livewire verfügbar:', true);
|
||||
} else {
|
||||
console.log('Livewire verfügbar:', false);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@livewireScripts
|
||||
@fluxScripts
|
||||
@include('partials.theme-toggle-script')
|
||||
</body>
|
||||
|
|
|
|||
27
resources/views/components/media-thumb.blade.php
Normal file
27
resources/views/components/media-thumb.blade.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
@props(['url' => '', 'size' => 'h-10 w-10'])
|
||||
|
||||
@php
|
||||
$url = (string) $url;
|
||||
$path = parse_url($url, PHP_URL_PATH) ?? '';
|
||||
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
$isVideo = in_array($ext, ['mp4', 'webm', 'mov', 'm4v', 'ogv'], true);
|
||||
@endphp
|
||||
|
||||
<div {{ $attributes->merge(['class' => "relative {$size} shrink-0 flex items-center justify-center overflow-hidden rounded-lg border border-zinc-200 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800"]) }}>
|
||||
@if($url === '')
|
||||
<x-heroicon-o-photo class="h-5 w-5 text-zinc-300 dark:text-zinc-600" />
|
||||
@elseif($isVideo)
|
||||
<video class="h-full w-full object-cover" preload="metadata" muted playsinline>
|
||||
<source src="{{ $url }}#t=1">
|
||||
</video>
|
||||
<span class="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<x-heroicon-s-play class="h-4 w-4 text-white/90 drop-shadow" />
|
||||
</span>
|
||||
@else
|
||||
<img src="{{ $url }}" alt="" class="h-full w-full object-cover" loading="lazy"
|
||||
onerror="this.style.display='none';this.nextElementSibling.classList.remove('hidden');" />
|
||||
<span class="hidden">
|
||||
<x-heroicon-o-link class="h-5 w-5 text-zinc-400" />
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -220,6 +220,13 @@
|
|||
<flux:icon.play class="w-3 h-3" />
|
||||
{{ __('Test-URL') }}
|
||||
</a>
|
||||
<flux:button wire:click="rotatePreviewToken({{ $display->id }})"
|
||||
wire:confirm="Vorschau-Link neu erzeugen? Der bisherige Link wird damit ungültig."
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
icon="arrow-path">
|
||||
{{ __('Link erneuern') }}
|
||||
</flux:button>
|
||||
<flux:button wire:click="publishDraft({{ $display->id }})"
|
||||
wire:confirm="Diesen Entwurf veröffentlichen und den Live-Stand ersetzen?"
|
||||
size="xs"
|
||||
|
|
@ -342,17 +349,23 @@
|
|||
@if($availableVersions->isNotEmpty())
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-1">
|
||||
<flux:select wire:model="addVersionSelect" placeholder="Modul hinzufügen...">
|
||||
<flux:select wire:model="versionsToAdd"
|
||||
variant="listbox"
|
||||
multiple
|
||||
searchable
|
||||
placeholder="Module hinzufügen..."
|
||||
selected-suffix="Module ausgewählt">
|
||||
@foreach($availableVersions as $version)
|
||||
<option value="{{ $version->id }}">{{ $version->name }} ({{ $version->type->label() }})</option>
|
||||
<flux:select.option value="{{ $version->id }}">{{ $version->name }} ({{ $version->type->label() }})</flux:select.option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
</div>
|
||||
<flux:button wire:click="addVersion"
|
||||
<flux:button wire:click="addSelectedVersions"
|
||||
type="button"
|
||||
icon="plus"
|
||||
size="sm"
|
||||
variant="ghost">
|
||||
{{ __('Hinzufügen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@else
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ use Flux\Flux;
|
|||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
use Livewire\WithFileUploads;
|
||||
use Livewire\WithPagination;
|
||||
use function Livewire\Volt\{layout, title, state, computed, on, uses};
|
||||
|
||||
layout('components.layouts.app');
|
||||
title('Display-Mediathek');
|
||||
uses([WithFileUploads::class]);
|
||||
uses([WithFileUploads::class, WithPagination::class]);
|
||||
|
||||
state([
|
||||
'search' => '',
|
||||
|
|
@ -47,12 +48,16 @@ $media = computed(
|
|||
default => $q,
|
||||
})
|
||||
->when($this->filterCollection, fn ($q) => $q->inCollection($this->filterCollection))
|
||||
->when($this->search, fn ($q) => $q->where('filename', 'like', "%{$this->search}%")
|
||||
->orWhere('title', 'like', "%{$this->search}%"))
|
||||
->when($this->search, fn ($q) => $q->search($this->search))
|
||||
->orderByDesc('created_at')
|
||||
->paginate(48),
|
||||
);
|
||||
|
||||
$updatedSearch = fn () => $this->resetPage();
|
||||
$updatedFilterType = fn () => $this->resetPage();
|
||||
$updatedFilterSource = fn () => $this->resetPage();
|
||||
$updatedFilterCollection = fn () => $this->resetPage();
|
||||
|
||||
$collections = computed(fn () => DisplayMedia::query()
|
||||
->whereNotNull('collection')
|
||||
->where('collection', '!=', '')
|
||||
|
|
@ -283,27 +288,37 @@ $closeDetail = function () {
|
|||
class="group relative cursor-pointer overflow-hidden rounded-lg border transition-all
|
||||
{{ $editingId === $item->id ? 'border-blue-500 ring-2 ring-blue-200 dark:ring-blue-800' : 'border-zinc-200 hover:border-zinc-400 dark:border-zinc-700' }}"
|
||||
wire:click="startEdit({{ $item->id }})">
|
||||
<div class="aspect-square bg-zinc-100 dark:bg-zinc-800">
|
||||
@if ($item->isImage() && $item->isUpload())
|
||||
<img src="{{ $item->getThumbnailUrl() }}"
|
||||
@php
|
||||
$thumbSrc = $item->getThumbnailUrl() ?? ($item->isImage() && $item->isExternal() ? $item->external_url : null);
|
||||
$videoFrameSrc = (! $thumbSrc && $item->isVideo() && $item->isUpload()) ? $item->getUrl() : null;
|
||||
@endphp
|
||||
<div class="relative aspect-square bg-zinc-100 dark:bg-zinc-800">
|
||||
@if ($thumbSrc)
|
||||
<img src="{{ $thumbSrc }}"
|
||||
alt="{{ $item->alt_text ?? $item->filename }}"
|
||||
class="h-full w-full object-cover" loading="lazy" />
|
||||
@elseif ($videoFrameSrc)
|
||||
<video class="h-full w-full object-cover" preload="metadata" muted playsinline>
|
||||
<source src="{{ $videoFrameSrc }}#t=1" type="{{ $item->mime_type }}">
|
||||
</video>
|
||||
@elseif ($item->isVideo())
|
||||
<div class="flex h-full w-full flex-col items-center justify-center gap-2 text-purple-400">
|
||||
<x-heroicon-o-film class="h-10 w-10" />
|
||||
<span class="text-xs">Video</span>
|
||||
</div>
|
||||
@elseif ($item->isExternal() && $item->isImage())
|
||||
<div class="flex h-full w-full flex-col items-center justify-center gap-2 text-blue-400">
|
||||
<x-heroicon-o-photo class="h-10 w-10" />
|
||||
<span class="text-xs">Extern</span>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex h-full w-full flex-col items-center justify-center gap-2 text-zinc-400">
|
||||
<x-heroicon-o-link class="h-10 w-10" />
|
||||
<span class="text-xs">Link</span>
|
||||
</div>
|
||||
@endif
|
||||
@if ($item->isVideo() && ($thumbSrc || $videoFrameSrc))
|
||||
<div class="pointer-events-none absolute inset-0 flex items-center justify-center">
|
||||
<span class="flex h-10 w-10 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/30 backdrop-blur-sm">
|
||||
<x-heroicon-s-play class="h-5 w-5" />
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 p-2">
|
||||
@if ($item->isVideo())
|
||||
|
|
@ -352,9 +367,17 @@ $closeDetail = function () {
|
|||
class="cursor-pointer transition {{ $editingId === $item->id ? 'bg-blue-50 dark:bg-blue-900/20' : 'hover:bg-zinc-50 dark:hover:bg-zinc-800/50' }}"
|
||||
wire:click="startEdit({{ $item->id }})">
|
||||
<td class="px-3 py-1.5">
|
||||
<div class="flex h-8 w-8 items-center justify-center overflow-hidden rounded border border-zinc-200 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
@if ($item->isImage() && $item->isUpload())
|
||||
<img src="{{ $item->getThumbnailUrl() }}" class="h-full w-full object-cover" loading="lazy" />
|
||||
@php
|
||||
$rowThumb = $item->getThumbnailUrl() ?? ($item->isImage() && $item->isExternal() ? $item->external_url : null);
|
||||
$rowVideoFrame = (! $rowThumb && $item->isVideo() && $item->isUpload()) ? $item->getUrl() : null;
|
||||
@endphp
|
||||
<div class="relative flex h-8 w-8 items-center justify-center overflow-hidden rounded border border-zinc-200 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
@if ($rowThumb)
|
||||
<img src="{{ $rowThumb }}" class="h-full w-full object-cover" loading="lazy" />
|
||||
@elseif ($rowVideoFrame)
|
||||
<video class="h-full w-full object-cover" preload="metadata" muted playsinline>
|
||||
<source src="{{ $rowVideoFrame }}#t=1" type="{{ $item->mime_type }}">
|
||||
</video>
|
||||
@elseif ($item->isVideo())
|
||||
<x-heroicon-s-film class="h-4 w-4 text-purple-500" />
|
||||
@else
|
||||
|
|
@ -418,17 +441,18 @@ $closeDetail = function () {
|
|||
|
||||
{{-- Preview --}}
|
||||
<div class="mb-4 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
@if ($editMedia->isImage() && $editMedia->isUpload())
|
||||
@if ($editMedia->isImage())
|
||||
<img src="{{ $editMedia->getUrl() }}" alt="{{ $editMedia->filename }}"
|
||||
class="w-full object-contain" style="max-height: 300px;" />
|
||||
@elseif ($editMedia->isVideo() && $editMedia->isUpload())
|
||||
<video controls class="w-full" style="max-height: 300px;">
|
||||
<video controls preload="metadata" class="w-full" style="max-height: 300px;"
|
||||
@if ($editMedia->getThumbnailUrl()) poster="{{ $editMedia->getThumbnailUrl() }}" @endif>
|
||||
<source src="{{ $editMedia->getUrl() }}" type="{{ $editMedia->mime_type }}">
|
||||
</video>
|
||||
@elseif ($editMedia->isExternal())
|
||||
<div class="flex flex-col items-center justify-center gap-3 py-8">
|
||||
<x-heroicon-o-link class="h-12 w-12 text-blue-400" />
|
||||
<span class="text-sm text-zinc-500">Externe Ressource</span>
|
||||
<span class="text-sm text-zinc-500">Externe Ressource (Vorschau nicht einbettbar)</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,20 @@
|
|||
<div class="flex items-end gap-3">
|
||||
<div class="flex-1">
|
||||
@if ($selectedMedia)
|
||||
@php
|
||||
$selectedThumb = $selectedMedia->getThumbnailUrl()
|
||||
?? ($selectedMedia->isImage() && $selectedMedia->isExternal() ? $selectedMedia->external_url : null);
|
||||
$selectedVideoFrame = (! $selectedThumb && $selectedMedia->isVideo() && $selectedMedia->isUpload()) ? $selectedMedia->getUrl() : null;
|
||||
@endphp
|
||||
<div class="flex items-center gap-3 rounded-lg border border-zinc-200 p-2 dark:border-zinc-700">
|
||||
@if ($selectedMedia->isImage() && $selectedMedia->isUpload())
|
||||
<img src="{{ $selectedMedia->getThumbnailUrl() }}"
|
||||
@if ($selectedThumb)
|
||||
<img src="{{ $selectedThumb }}"
|
||||
alt="{{ $selectedMedia->filename }}"
|
||||
class="h-16 w-16 rounded-md object-cover" />
|
||||
@elseif ($selectedVideoFrame)
|
||||
<video class="h-16 w-16 rounded-md object-cover" preload="metadata" muted playsinline>
|
||||
<source src="{{ $selectedVideoFrame }}#t=1" type="{{ $selectedMedia->mime_type }}">
|
||||
</video>
|
||||
@elseif ($selectedMedia->isVideo())
|
||||
<div class="flex h-16 w-16 items-center justify-center rounded-md bg-purple-50 dark:bg-purple-900/20">
|
||||
<x-heroicon-o-film class="h-8 w-8 text-purple-500" />
|
||||
|
|
@ -88,9 +97,18 @@
|
|||
{{ $value === $item->id ? 'border-blue-500 ring-2 ring-blue-200' : 'border-zinc-200 hover:border-blue-300 dark:border-zinc-700' }}"
|
||||
wire:click="selectMedia({{ $item->id }})">
|
||||
<div class="relative aspect-square bg-zinc-100 dark:bg-zinc-800">
|
||||
@if ($item->isImage() && $item->isUpload())
|
||||
<img src="{{ $item->getThumbnailUrl() }}"
|
||||
@php
|
||||
$pickThumb = $item->getThumbnailUrl()
|
||||
?? ($item->isImage() && $item->isExternal() ? $item->external_url : null);
|
||||
$pickVideoFrame = (! $pickThumb && $item->isVideo() && $item->isUpload()) ? $item->getUrl() : null;
|
||||
@endphp
|
||||
@if ($pickThumb)
|
||||
<img src="{{ $pickThumb }}"
|
||||
alt="{{ $item->filename }}" class="h-full w-full object-cover" loading="lazy" />
|
||||
@elseif ($pickVideoFrame)
|
||||
<video class="h-full w-full object-cover" preload="metadata" muted playsinline>
|
||||
<source src="{{ $pickVideoFrame }}#t=1" type="{{ $item->mime_type }}">
|
||||
</video>
|
||||
@elseif ($item->isVideo())
|
||||
<div class="flex h-full w-full items-center justify-center text-purple-500">
|
||||
<x-heroicon-o-film class="h-10 w-10" />
|
||||
|
|
|
|||
|
|
@ -121,9 +121,13 @@
|
|||
type="video"
|
||||
label="Video aus Mediathek"
|
||||
:key="'picker-video-' . ($itemId ?? 'new')" />
|
||||
<flux:input wire:model="videoFilename" label="Video-Pfad / URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt Pfad/URL eingeben." />
|
||||
<flux:input wire:model="videoTitle" label="Titel (optional)" placeholder="z.B. Herbst Kollektion 2025" />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$videoFilename" />
|
||||
<flux:input wire:model.live.debounce.500ms="videoFilename" label="Video-Pfad / URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt Pfad/URL eingeben." class="flex-1" />
|
||||
</div>
|
||||
<flux:input wire:model="videoTitle" label="Name" placeholder="z.B. Herbst Kollektion 2025"
|
||||
description="Interner Name des Videos – wird nicht im Video eingeblendet." />
|
||||
<flux:input wire:model="videoPosition" type="number" min="0" max="100" label="Position (%)"
|
||||
description="Vertikale Position im Video (0 = oben, 100 = unten)" />
|
||||
<flux:checkbox wire:model="videoIsActive" label="Aktiv" />
|
||||
|
|
@ -140,22 +144,29 @@
|
|||
|
||||
{{-- Media fields (B2in) --}}
|
||||
@if($itemType === 'media')
|
||||
<flux:select wire:model="mediaType" label="Medientyp">
|
||||
<option value="image">Bild</option>
|
||||
<option value="video">Video</option>
|
||||
</flux:select>
|
||||
<flux:select wire:model="mediaCategory" label="Kategorie">
|
||||
<option value="immobilien">Immobilien</option>
|
||||
<option value="moebel">Möbel</option>
|
||||
</flux:select>
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="mediaUrl"
|
||||
:type="$mediaType === 'video' ? 'video' : 'image'"
|
||||
type="all"
|
||||
label="Aus Mediathek"
|
||||
:key="'picker-media-' . ($itemId ?? 'new')" />
|
||||
<flux:input wire:model="mediaUrl" label="Medien-URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt URL eingeben." />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$mediaUrl" />
|
||||
<flux:input wire:model.live.debounce.500ms="mediaUrl" label="Medien-URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt URL eingeben." class="flex-1" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:text class="text-sm">{{ __('Medientyp:') }}</flux:text>
|
||||
<flux:badge size="sm" :color="$mediaType === 'video' ? 'purple' : 'sky'">
|
||||
{{ $mediaType === 'video' ? __('Video') : __('Bild') }}
|
||||
</flux:badge>
|
||||
<flux:text class="text-xs text-zinc-400">{{ __('wird automatisch aus dem gewählten Medium erkannt') }}</flux:text>
|
||||
</div>
|
||||
<flux:select wire:model="mediaCategory" label="Kategorie">
|
||||
<option value="immobilien">Immobilien</option>
|
||||
<option value="moebel">Möbel</option>
|
||||
<option value="sonstiges">Sonstiges</option>
|
||||
</flux:select>
|
||||
<flux:input wire:model="mediaHeadline" label="Überschrift" placeholder="z.B. Ihr Zuhause. Weltweit." />
|
||||
<flux:input wire:model="mediaSubline" label="Unterzeile" placeholder="z.B. Beratung und Vermittlung." />
|
||||
@if($mediaType === 'image')
|
||||
|
|
@ -165,47 +176,79 @@
|
|||
<flux:checkbox wire:model="mediaIsActive" label="Aktiv" />
|
||||
@endif
|
||||
|
||||
{{-- Slide fields (Offers) --}}
|
||||
{{-- Slide fields (Offers) – einheitliches Detail-Layout mit Ein-/Ausblende-Schaltern --}}
|
||||
@if($itemType === 'slide')
|
||||
{{-- Basis --}}
|
||||
<flux:select wire:model.live="slideType" label="Slide-Typ">
|
||||
<option value="intro">Intro</option>
|
||||
<option value="product-hero">Produkt-Hero</option>
|
||||
<option value="product-details">Produkt-Details</option>
|
||||
<option value="product-impulse">Produkt-Impuls</option>
|
||||
</flux:select>
|
||||
<flux:input wire:model="slideDuration" type="number" min="1000" label="Dauer (ms)" />
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="slideImageUrl"
|
||||
type="image"
|
||||
label="Bild aus Mediathek"
|
||||
:key="'picker-slide-' . ($itemId ?? 'new')" />
|
||||
<flux:input wire:model="slideImageUrl" label="Bild-URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt URL eingeben." />
|
||||
<flux:input wire:model="slideBadge" label="Badge-Text" placeholder="z.B. Einzelstück" />
|
||||
<flux:input wire:model="slideEyebrow" label="Eyebrow" placeholder="z.B. Hersteller: Sudbrock" />
|
||||
<flux:input wire:model="slideTitle" label="Titel" placeholder="z.B. GOYA Sideboard" />
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">
|
||||
{{ __('Jedes Angebot nutzt dasselbe Detail-Layout. Über die Schalter blendest du einzelne Bausteine ein oder aus und befüllst sie mit Inhalten.') }}
|
||||
</flux:text>
|
||||
|
||||
{{-- Intro-spezifisch --}}
|
||||
@if($slideType === 'intro')
|
||||
<flux:input wire:model="slideDisclaimer" label="Disclaimer" placeholder="z.B. Zwischenverkauf vorbehalten" />
|
||||
<flux:checkbox wire:model="slideShowBrandText" label="Brand-Text anzeigen" />
|
||||
@if($slideShowBrandText)
|
||||
<flux:input wire:model="slideBrandTagline" label="Brand-Tagline" placeholder="z.B. Planung • Beratung • Lieferung & Montage" />
|
||||
{{-- Logo & Marke (Kopfbereich) --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Logo & Marke') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowLogo" label="Logo & Marken-Text anzeigen"
|
||||
description="Kopfbereich oben mit Logo, Marken-Text und Tagline." />
|
||||
@if($slideShowLogo)
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="slideLogoUrl"
|
||||
type="image"
|
||||
label="Logo aus Mediathek"
|
||||
:key="'picker-slide-logo-' . ($itemId ?? 'new')" />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$slideLogoUrl" />
|
||||
<flux:input wire:model.live.debounce.500ms="slideLogoUrl" label="Logo-URL" placeholder="Standard: CABINET-Logo"
|
||||
description="Leer = Standard-Logo wird genutzt." class="flex-1" />
|
||||
</div>
|
||||
<flux:input wire:model="slideBrandText" label="Marken-Text" placeholder="z.B. Bielefeld"
|
||||
description="Text direkt neben dem Logo (optional)." />
|
||||
<flux:input wire:model="slideBrandTagline" label="Tagline" placeholder="z.B. Planung • Beratung • Lieferung & Montage"
|
||||
description="Rechts im Kopfbereich (optional)." />
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Product-Hero --}}
|
||||
@if($slideType === 'product-hero')
|
||||
<flux:input wire:model="slidePrice" label="Preis" placeholder="z.B. 489 €" />
|
||||
<flux:input wire:model="slideOriginalPrice" label="Originalpreis" placeholder="z.B. statt 4.744 €" />
|
||||
@endif
|
||||
{{-- Bild & Badge (wichtigstes Element – farblich hervorgehoben) --}}
|
||||
<div class="space-y-4 rounded-xl border-2 border-blue-300 bg-blue-50/60 p-4 ring-1 ring-blue-200 dark:border-blue-700 dark:bg-blue-950/30 dark:ring-blue-900">
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:heading size="sm">{{ __('Bild & Badge') }}</flux:heading>
|
||||
<flux:badge color="blue" size="sm">{{ __('Wichtigstes Element') }}</flux:badge>
|
||||
</div>
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="slideImageUrl"
|
||||
type="image"
|
||||
label="Bild aus Mediathek"
|
||||
:key="'picker-slide-' . ($itemId ?? 'new')" />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$slideImageUrl" />
|
||||
<flux:input wire:model.live.debounce.500ms="slideImageUrl" label="Bild-URL" placeholder="Wird automatisch gesetzt oder manuell eingeben..."
|
||||
description="Über die Mediathek auswählen oder direkt URL eingeben." class="flex-1" />
|
||||
</div>
|
||||
<flux:switch wire:model.live="slideShowBadge" label="Badge anzeigen" description="Kleine Markierung über dem Bild." />
|
||||
@if($slideShowBadge)
|
||||
<flux:input wire:model="slideBadge" label="Badge-Text" placeholder="z.B. Einzelstück" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Product-Details --}}
|
||||
@if($slideType === 'product-details')
|
||||
<div>
|
||||
<flux:heading size="sm" class="mb-2">{{ __('Aufzählungspunkte') }}</flux:heading>
|
||||
{{-- Texte --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Texte') }}</flux:heading>
|
||||
<flux:input wire:model="slideTitle" label="Titel" placeholder="z.B. GOYA Sideboard"
|
||||
description="Hauptüberschrift des Angebots (Zeilenumbruch mit Enter möglich)." />
|
||||
<flux:switch wire:model.live="slideShowEyebrow" label="Eyebrow anzeigen" description="Kleine Überzeile über dem Titel." />
|
||||
@if($slideShowEyebrow)
|
||||
<flux:input wire:model="slideEyebrow" label="Eyebrow" placeholder="z.B. Hersteller: Sudbrock" />
|
||||
@endif
|
||||
<flux:switch wire:model.live="slideShowSubline" label="Unterzeile anzeigen" />
|
||||
@if($slideShowSubline)
|
||||
<flux:input wire:model="slideSubline" label="Unterzeile" placeholder="z.B. Heute mitnehmen" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Aufzählung --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Aufzählungspunkte') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowBullets" label="Aufzählung anzeigen" description="Liste mit Stichpunkten, z.B. Produktdetails." />
|
||||
@if($slideShowBullets)
|
||||
<div class="space-y-2">
|
||||
@foreach($slideBullets as $i => $bullet)
|
||||
<div class="flex items-center gap-2" wire:key="bullet-{{ $i }}">
|
||||
|
|
@ -214,30 +257,58 @@
|
|||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<flux:button wire:click="addBullet" size="xs" variant="ghost" icon="plus" class="mt-2">
|
||||
<flux:button wire:click="addBullet" size="xs" variant="ghost" icon="plus">
|
||||
{{ __('Punkt hinzufügen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Product-Impulse --}}
|
||||
@if($slideType === 'product-impulse')
|
||||
<flux:input wire:model="slideSubline" label="Subline" placeholder="z.B. Heute mitnehmen" />
|
||||
<flux:input wire:model="slidePrice" label="Preis" placeholder="z.B. 199 €" />
|
||||
<flux:input wire:model="slideTagText" label="Tag-Text" placeholder="z.B. Im Store verfügbar" />
|
||||
@endif
|
||||
|
||||
{{-- QR --}}
|
||||
<div class="border-t border-zinc-200 dark:border-zinc-700 pt-4 mt-2">
|
||||
<flux:heading size="sm" class="mb-3">{{ __('QR-Code & Kontakt') }}</flux:heading>
|
||||
<div class="space-y-4">
|
||||
<flux:input wire:model="slideQrUrl" label="QR-URL" placeholder="z.B. https://cabinet-bielefeld.de" />
|
||||
<flux:input wire:model="slideQrTitle" label="QR-Titel" placeholder="z.B. Reservieren" />
|
||||
<flux:input wire:model="slideContact" label="Kontakt" placeholder="z.B. 0521 98620100 / Tel. oder WhatsApp" />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<flux:checkbox wire:model="slideIsActive" label="Aktiv" />
|
||||
{{-- Preis --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Preis') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowPrice" label="Preis anzeigen" />
|
||||
@if($slideShowPrice)
|
||||
<flux:input wire:model="slidePrice" label="Preis" placeholder="z.B. 489 €" />
|
||||
<flux:input wire:model="slideOriginalPrice" label="Originalpreis (optional)" placeholder="z.B. statt 4.744 €" />
|
||||
@if(trim($slideOriginalPrice) !== '')
|
||||
<flux:switch wire:model.live="slideStrikeOriginalPrice" label="Streichpreis"
|
||||
description="Originalpreis wird rot durchgestrichen dargestellt." />
|
||||
@endif
|
||||
<flux:input wire:model="slideTagText" label="Hinweis-Tag (optional)" placeholder="z.B. Im Store verfügbar" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Hinweis --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Hinweis') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowDisclaimer" label="Disclaimer anzeigen" />
|
||||
@if($slideShowDisclaimer)
|
||||
<flux:input wire:model="slideDisclaimer" label="Disclaimer" placeholder="z.B. Zwischenverkauf vorbehalten" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- QR & Kontakt --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('QR-Code & Kontakt') }}</flux:heading>
|
||||
<flux:switch wire:model.live="slideShowQr" label="QR-Code anzeigen" />
|
||||
@if($slideShowQr)
|
||||
<flux:input wire:model="slideQrUrl" label="QR-URL" placeholder="z.B. https://cabinet-bielefeld.de"
|
||||
description="Leer = es wird die Web/QR-URL aus den Einstellungen genutzt." />
|
||||
<flux:input wire:model="slideQrTitle" label="QR-Titel" placeholder="z.B. Reservieren" />
|
||||
@endif
|
||||
<flux:switch wire:model.live="slideShowContact" label="Kontakt anzeigen" />
|
||||
@if($slideShowContact)
|
||||
<flux:input wire:model="slideContact" label="Kontakt" placeholder="z.B. 0521 98620100 / Tel. oder WhatsApp" />
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Anzeige --}}
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Anzeige') }}</flux:heading>
|
||||
<flux:input wire:model="slideDuration" type="number" min="1000" step="500" label="Dauer (ms)"
|
||||
description="Wie lange dieses Angebot eingeblendet wird." />
|
||||
<flux:switch wire:model="slideIsActive" label="Aktiv" description="Inaktive Angebote werden im Display übersprungen." />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="space-y-3 border-t border-zinc-200 pt-6 dark:border-zinc-700">
|
||||
|
|
@ -246,22 +317,37 @@
|
|||
<flux:subheading>{{ __('Zeigt nur den aktuell bearbeiteten Inhalt im Display-Player') }}</flux:subheading>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto aspect-[9/16] w-full max-w-[320px] overflow-hidden rounded-xl border border-zinc-200 bg-black shadow-sm dark:border-zinc-700">
|
||||
{{-- Stable iframe element: only its `src` changes between the
|
||||
"new" (about:blank) and "saved" state. Swapping the element
|
||||
structure inside the teleported Flux modal crashes Livewire's
|
||||
morph/cleanup, so we keep the DOM shape constant. --}}
|
||||
<div class="relative mx-auto aspect-[9/16] w-full max-w-[320px] overflow-hidden rounded-xl border border-zinc-200 bg-black shadow-sm dark:border-zinc-700">
|
||||
<iframe
|
||||
wire:key="item-modal-module-preview-{{ $previewFrameRefreshCounter }}"
|
||||
src="{{ $this->itemPreviewUrl() }}"
|
||||
wire:key="item-modal-preview"
|
||||
src="{{ $itemId ? $this->itemPreviewUrl() : 'about:blank' }}"
|
||||
class="h-full w-full border-0"
|
||||
loading="lazy"
|
||||
title="{{ __('Einzel-Vorschau im Bearbeiten-Dialog') }}"
|
||||
></iframe>
|
||||
|
||||
@unless($itemId)
|
||||
<div class="absolute inset-0 flex items-center justify-center bg-zinc-50 text-center dark:bg-zinc-900">
|
||||
<div class="px-6 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
<flux:icon.eye class="mx-auto mb-2 h-8 w-8 opacity-40" />
|
||||
{{ __('Die Vorschau erscheint, sobald der Inhalt gespeichert wurde.') }}
|
||||
</div>
|
||||
</div>
|
||||
@endunless
|
||||
</div>
|
||||
|
||||
<a href="{{ $this->itemPreviewUrl() }}"
|
||||
target="_blank"
|
||||
class="mx-auto flex max-w-[320px] items-center justify-center gap-1 text-xs text-blue-600 hover:underline dark:text-blue-400">
|
||||
<flux:icon.arrow-top-right-on-square class="w-3 h-3" />
|
||||
{{ __('Vollbild öffnen') }}
|
||||
</a>
|
||||
@if($itemId)
|
||||
<a href="{{ $this->itemPreviewUrl() }}"
|
||||
target="_blank"
|
||||
class="mx-auto flex max-w-[320px] items-center justify-center gap-1 text-xs text-blue-600 hover:underline dark:text-blue-400">
|
||||
<flux:icon.arrow-top-right-on-square class="w-3 h-3" />
|
||||
{{ __('Vollbild öffnen') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap justify-end gap-3 border-t border-zinc-200 pt-4 dark:border-zinc-700">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,15 @@
|
|||
</x-success-alert>
|
||||
@endif
|
||||
|
||||
@if (session()->has('error'))
|
||||
<div class="mb-4 rounded-lg border border-red-200 bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20">
|
||||
<div class="flex items-start gap-3">
|
||||
<flux:icon.exclamation-circle class="mt-0.5 h-5 w-5 shrink-0 text-red-600 dark:text-red-400" />
|
||||
<p class="text-sm font-medium text-red-800 dark:text-red-200">{{ session('error') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@
|
|||
<flux:card>
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Slides') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Angebots-Slides werden in der angegebenen Reihenfolge angezeigt') }}</flux:subheading>
|
||||
<flux:heading size="lg">{{ __('Angebote') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Angebote werden im einheitlichen Detail-Layout in der angegebenen Reihenfolge angezeigt') }}</flux:subheading>
|
||||
</div>
|
||||
<flux:button wire:click="openItemModal(null, 'slide')" icon="plus">
|
||||
{{ __('Slide hinzufügen') }}
|
||||
{{ __('Angebot hinzufügen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
@if($slides->isEmpty())
|
||||
<div class="text-center py-12 text-zinc-500 dark:text-zinc-400">
|
||||
<flux:icon.presentation-chart-bar class="w-16 h-16 mx-auto mb-4 opacity-50" />
|
||||
<p>{{ __('Noch keine Slides vorhanden.') }}</p>
|
||||
<p>{{ __('Noch keine Angebote vorhanden.') }}</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-3">
|
||||
|
|
@ -42,29 +42,30 @@
|
|||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
@php
|
||||
$c = $item->content;
|
||||
$enabledBlocks = collect([
|
||||
'Badge' => ($c['show_badge'] ?? ! empty($c['badge_text'])) && ! empty($c['badge_text']),
|
||||
'Aufzählung' => ($c['show_bullets'] ?? ! empty($c['bullets'])) && ! empty($c['bullets']),
|
||||
'Preis' => ($c['show_price'] ?? ! empty($c['price'])) && ! empty($c['price']),
|
||||
'QR' => ($c['show_qr'] ?? ! empty($c['qr_url'])) && ! empty($c['qr_url']),
|
||||
'Kontakt' => ($c['show_contact'] ?? ! empty($c['contact'])) && ! empty($c['contact']),
|
||||
])->filter()->keys();
|
||||
@endphp
|
||||
<div class="flex items-center gap-3 mb-1">
|
||||
<flux:badge :color="$item->is_active ? 'green' : 'zinc'" size="sm">
|
||||
{{ $item->is_active ? __('Aktiv') : __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
<flux:badge color="amber" size="sm">
|
||||
{{ match($item->content['type'] ?? '') {
|
||||
'intro' => 'Intro',
|
||||
'product-hero' => 'Produkt-Hero',
|
||||
'product-details' => 'Produkt-Details',
|
||||
'product-impulse' => 'Produkt-Impuls',
|
||||
default => $item->content['type'] ?? '–',
|
||||
} }}
|
||||
</flux:badge>
|
||||
<span class="font-semibold text-sm">{{ $item->content['title'] ?? '–' }}</span>
|
||||
<span class="font-semibold text-sm truncate">{{ $c['title'] ?? '–' }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-zinc-600 dark:text-zinc-400 space-x-4">
|
||||
@if(!empty($item->content['price']))
|
||||
<span class="font-medium">{{ $item->content['price'] }}</span>
|
||||
@endif
|
||||
<span>{{ number_format(($item->content['duration'] ?? 8000) / 1000, 1) }}s</span>
|
||||
@if(!empty($item->content['badge_text']))
|
||||
<span>{{ $item->content['badge_text'] }}</span>
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs text-zinc-600 dark:text-zinc-400">
|
||||
<span>{{ number_format(($c['duration'] ?? 8000) / 1000, 1) }}s</span>
|
||||
@if(!empty($c['price']) && ($c['show_price'] ?? ! empty($c['price'])))
|
||||
<span class="font-medium">{{ $c['price'] }}</span>
|
||||
@endif
|
||||
@foreach($enabledBlocks as $block)
|
||||
<flux:badge color="zinc" size="sm">{{ $block }}</flux:badge>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,74 @@
|
|||
@if($version->type->value === 'b2in')
|
||||
@php
|
||||
$footerShown = ($settings['show_footer'] ?? true) !== false;
|
||||
$showLogo = ($settings['show_logo'] ?? true) !== false;
|
||||
$showClaim = ($settings['show_claim'] ?? true) !== false;
|
||||
$logoPos = $settings['logo_position'] ?? 'top-left';
|
||||
$claimPos = $settings['claim_position'] ?? 'top-right';
|
||||
$brandPositions = [
|
||||
'top-left' => __('Oben links'),
|
||||
'top-right' => __('Oben rechts'),
|
||||
'bottom-left' => __('Unten links'),
|
||||
'bottom-right' => __('Unten rechts'),
|
||||
];
|
||||
@endphp
|
||||
<flux:select wire:model="settings.theme" label="Theme">
|
||||
<option value="dark">Dark</option>
|
||||
<option value="light">Light</option>
|
||||
</flux:select>
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Header') }}</flux:heading>
|
||||
<flux:heading size="sm">{{ __('Marke') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Logo und Claim. Standardmäßig oben im Header. Die Ecken lassen sich frei wählen.') }}</flux:subheading>
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="settings.header_logo_url"
|
||||
type="image"
|
||||
label="Header-Logo aus Mediathek"
|
||||
label="Logo aus Mediathek"
|
||||
:key="'picker-b2in-header-logo-' . $context . '-' . $version->id" />
|
||||
<flux:input wire:model="settings.header_logo_url" label="Header-Logo URL" placeholder="../assets/b2in-logo-positive.svg" />
|
||||
<div class="flex items-end gap-3">
|
||||
<x-media-thumb :url="$settings['header_logo_url'] ?? ''" />
|
||||
<flux:input wire:model.live.debounce.500ms="settings.header_logo_url" label="Logo URL" placeholder="../assets/b2in-logo-positive.svg" class="flex-1" />
|
||||
</div>
|
||||
<flux:input wire:model="settings.header_claim" label="Claim" placeholder="Connecting Design & Property" />
|
||||
|
||||
<div class="flex flex-wrap gap-6">
|
||||
<flux:switch wire:model.live="settings.show_logo" label="Logo anzeigen" />
|
||||
<flux:switch wire:model.live="settings.show_claim" label="Claim anzeigen" />
|
||||
</div>
|
||||
|
||||
@if($showLogo)
|
||||
<flux:select wire:model.live="settings.logo_position" label="Logo-Position">
|
||||
@foreach($brandPositions as $value => $label)
|
||||
<option value="{{ $value }}" @disabled($footerShown && str_starts_with($value, 'bottom'))>{{ $label }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
@endif
|
||||
|
||||
@if($showClaim)
|
||||
<flux:select wire:model.live="settings.claim_position" label="Claim-Position">
|
||||
@foreach($brandPositions as $value => $label)
|
||||
<option value="{{ $value }}"
|
||||
@disabled(($showLogo && $value === $logoPos) || ($footerShown && str_starts_with($value, 'bottom')))>
|
||||
{{ $label }}{{ ($showLogo && $value === $logoPos) ? ' '.__('(Logo)') : '' }}
|
||||
</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
@endif
|
||||
|
||||
@if($footerShown)
|
||||
<flux:callout variant="secondary" icon="information-circle">
|
||||
<flux:callout.text>{{ __('Untere Ecken sind nur verfügbar, wenn der Footer ausgeblendet ist.') }}</flux:callout.text>
|
||||
</flux:callout>
|
||||
@endif
|
||||
</div>
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Footer & QR') }}</flux:heading>
|
||||
<flux:switch wire:model.live="settings.show_footer" label="Footer anzeigen"
|
||||
description="Blendet die Fußzeile mit Domain, Name und QR-Code ein oder aus." />
|
||||
<flux:input wire:model="settings.footer_prefix" label="Footer-Präfix" placeholder="by" />
|
||||
<flux:input wire:model="settings.footer_name" label="Footer Name" placeholder="z.B. Marcel Scheibe" />
|
||||
<flux:input wire:model="settings.footer_url" label="Footer Domain" placeholder="z.B. b2in.de" />
|
||||
<flux:input wire:model="settings.qr_url" label="QR-URL (optional)" placeholder="https://b2in.de"
|
||||
<flux:input wire:model="settings.footer_url" label="Footer Domain" placeholder="z.B. b2in.eu" />
|
||||
<flux:input wire:model="settings.qr_url" label="QR-URL (optional)" placeholder="https://b2in.e"
|
||||
description="Leer = QR-Code nutzt die Footer-Domain." />
|
||||
</div>
|
||||
<flux:select wire:model="settings.transition.type" label="Transition">
|
||||
|
|
@ -32,30 +81,14 @@
|
|||
<flux:checkbox wire:model="settings.display_active" label="Display aktiv" />
|
||||
@elseif($version->type->value === 'offers')
|
||||
<flux:checkbox wire:model="settings.loop" label="Endlosschleife" />
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Branding') }}</flux:heading>
|
||||
<livewire:admin.cms.display-media-picker
|
||||
:value="null"
|
||||
field="settings.logo_url"
|
||||
type="image"
|
||||
label="Logo aus Mediathek"
|
||||
:key="'picker-offers-logo-' . $context . '-' . $version->id" />
|
||||
<flux:input wire:model="settings.logo_url" label="Logo URL" placeholder="../logo-cabinet-300.png" />
|
||||
<flux:input wire:model="settings.brand_text" label="Brand-Text" placeholder="Bielefeld" />
|
||||
</div>
|
||||
<div class="space-y-4 rounded-xl border border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<flux:heading size="sm">{{ __('Footer & QR für alle Slides') }}</flux:heading>
|
||||
<flux:input wire:model="settings.footer_claim" label="Footer-Claim" placeholder="z.B. Planung • Beratung • Lieferung & Montage" />
|
||||
<flux:input wire:model="settings.footer_url" label="Web/QR-URL" placeholder="https://cabinet-bielefeld.de"
|
||||
description="Wird als QR-Ziel genutzt, wenn der einzelne Slide keine eigene QR-URL hat." />
|
||||
<flux:input wire:model="settings.qr_default_title" label="Standard QR-Titel" placeholder="Kontakt" />
|
||||
<flux:input wire:model="settings.qr_subtitle" label="QR-Unterzeile" placeholder="QR scannen" />
|
||||
</div>
|
||||
<flux:select wire:model="settings.transition.type" label="Transition">
|
||||
<option value="fade">Fade</option>
|
||||
<option value="slide">Slide</option>
|
||||
</flux:select>
|
||||
<flux:input wire:model="settings.transition.duration" type="number" label="Transition-Dauer (ms)" />
|
||||
<flux:callout variant="secondary" icon="information-circle">
|
||||
<flux:callout.text>{{ __('Logo, Marken-Text, QR-Code und Kontakt werden je Angebot direkt am Element gepflegt.') }}</flux:callout.text>
|
||||
</flux:callout>
|
||||
@elseif($version->type->value === 'video-display')
|
||||
<flux:input wire:model="settings.qr_label" label="QR-Label im Footer" placeholder="Website" />
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -16,40 +16,43 @@
|
|||
</flux:button>
|
||||
</div>
|
||||
|
||||
@if($videos->isEmpty())
|
||||
@if ($videos->isEmpty())
|
||||
<div class="text-center py-12 text-zinc-500 dark:text-zinc-400">
|
||||
<flux:icon.film class="w-16 h-16 mx-auto mb-4 opacity-50" />
|
||||
<p>{{ __('Noch keine Videos vorhanden.') }}</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-3">
|
||||
@foreach($videos as $index => $item)
|
||||
@foreach ($videos as $index => $item)
|
||||
<div wire:key="item-{{ $item->id }}"
|
||||
class="flex items-center gap-4 p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 transition">
|
||||
class="flex items-center gap-4 p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 transition">
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
@if($index > 0)
|
||||
<flux:button wire:click="moveItem({{ $item->id }}, 'up')" size="xs" variant="ghost" icon="chevron-up" class="text-zinc-400 hover:text-zinc-600"></flux:button>
|
||||
@if ($index > 0)
|
||||
<flux:button wire:click="moveItem({{ $item->id }}, 'up')" size="xs" variant="ghost"
|
||||
icon="chevron-up" class="text-zinc-400 hover:text-zinc-600"></flux:button>
|
||||
@endif
|
||||
@if($index < count($videos) - 1)
|
||||
<flux:button wire:click="moveItem({{ $item->id }}, 'down')" size="xs" variant="ghost" icon="chevron-down" class="text-zinc-400 hover:text-zinc-600"></flux:button>
|
||||
@if ($index < count($videos) - 1)
|
||||
<flux:button wire:click="moveItem({{ $item->id }}, 'down')" size="xs"
|
||||
variant="ghost" icon="chevron-down" class="text-zinc-400 hover:text-zinc-600">
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex h-16 w-12 shrink-0 items-center justify-center rounded-lg bg-black text-[10px] font-semibold uppercase text-white">
|
||||
Video
|
||||
</div>
|
||||
<x-media-thumb :url="$item->content['filename'] ?? ''" size="h-16 w-12" />
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-3 mb-1">
|
||||
<flux:badge :color="$item->is_active ? 'green' : 'zinc'" size="sm">
|
||||
{{ $item->is_active ? __('Aktiv') : __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
<span class="font-semibold text-sm">{{ $item->content['title'] ?? $item->content['filename'] ?? '–' }}</span>
|
||||
<span
|
||||
class="font-semibold text-sm">{{ $item->content['title'] ?? ($item->content['filename'] ?? '–') }}</span>
|
||||
</div>
|
||||
@php
|
||||
$videoSource = $item->content['filename'] ?? '';
|
||||
$isMediaLibrarySource = str_starts_with($videoSource, '/storage/') || str_starts_with($videoSource, 'http');
|
||||
$isMediaLibrarySource =
|
||||
str_starts_with($videoSource, '/storage/') || str_starts_with($videoSource, 'http');
|
||||
@endphp
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs text-zinc-600 dark:text-zinc-400">
|
||||
<flux:badge size="sm" :color="$isMediaLibrarySource ? 'sky' : 'zinc'">
|
||||
|
|
@ -61,9 +64,13 @@
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:button wire:click="toggleItemStatus({{ $item->id }})" size="sm" variant="ghost" :icon="$item->is_active ? 'eye-slash' : 'eye'"></flux:button>
|
||||
<flux:button wire:click="openItemModal({{ $item->id }})" size="sm" variant="ghost" icon="pencil"></flux:button>
|
||||
<flux:button wire:click="deleteItem({{ $item->id }})" wire:confirm="Möchten Sie diesen Eintrag wirklich löschen?" size="sm" variant="ghost" icon="trash" class="text-red-600 hover:text-red-700"></flux:button>
|
||||
<flux:button wire:click="toggleItemStatus({{ $item->id }})" size="sm" variant="ghost"
|
||||
:icon="$item->is_active ? 'eye-slash' : 'eye'"></flux:button>
|
||||
<flux:button wire:click="openItemModal({{ $item->id }})" size="sm" variant="ghost"
|
||||
icon="pencil"></flux:button>
|
||||
<flux:button wire:click="deleteItem({{ $item->id }})"
|
||||
wire:confirm="Möchten Sie diesen Eintrag wirklich löschen?" size="sm" variant="ghost"
|
||||
icon="trash" class="text-red-600 hover:text-red-700"></flux:button>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
|
@ -76,34 +83,40 @@
|
|||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Footer-Inhalte') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Inhalte werden im Footer rotiert') }}</flux:subheading>
|
||||
<flux:subheading>{{ __('Inhalte werden im Footer rotiert / ohne Inhalte bleibt der untere Teil frei.') }}
|
||||
</flux:subheading>
|
||||
</div>
|
||||
<flux:button wire:click="openItemModal(null, 'footer')" icon="plus">
|
||||
{{ __('Inhalt hinzufügen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
@if($footers->isEmpty())
|
||||
@if ($footers->isEmpty())
|
||||
<div class="text-center py-12 text-zinc-500 dark:text-zinc-400">
|
||||
<flux:icon.document-text class="w-16 h-16 mx-auto mb-4 opacity-50" />
|
||||
<p>{{ __('Noch keine Footer-Inhalte vorhanden.') }}</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-3">
|
||||
@foreach($footers as $index => $item)
|
||||
@foreach ($footers as $index => $item)
|
||||
<div wire:key="item-{{ $item->id }}"
|
||||
class="flex items-center gap-4 p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 transition">
|
||||
class="flex items-center gap-4 p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 transition">
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
@if($index > 0)
|
||||
<flux:button wire:click="moveItem({{ $item->id }}, 'up')" size="xs" variant="ghost" icon="chevron-up" class="text-zinc-400 hover:text-zinc-600"></flux:button>
|
||||
@if ($index > 0)
|
||||
<flux:button wire:click="moveItem({{ $item->id }}, 'up')" size="xs"
|
||||
variant="ghost" icon="chevron-up" class="text-zinc-400 hover:text-zinc-600">
|
||||
</flux:button>
|
||||
@endif
|
||||
@if($index < count($footers) - 1)
|
||||
<flux:button wire:click="moveItem({{ $item->id }}, 'down')" size="xs" variant="ghost" icon="chevron-down" class="text-zinc-400 hover:text-zinc-600"></flux:button>
|
||||
@if ($index < count($footers) - 1)
|
||||
<flux:button wire:click="moveItem({{ $item->id }}, 'down')" size="xs"
|
||||
variant="ghost" icon="chevron-down" class="text-zinc-400 hover:text-zinc-600">
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex h-16 w-12 shrink-0 flex-col justify-end rounded-lg bg-zinc-900 p-1 text-[8px] text-white">
|
||||
<div
|
||||
class="flex h-16 w-12 shrink-0 flex-col justify-end rounded-lg bg-zinc-900 p-1 text-[8px] text-white">
|
||||
<div class="truncate text-zinc-400">{{ $item->content['headline'] ?? 'Footer' }}</div>
|
||||
<div class="truncate font-semibold">{{ $item->content['subline'] ?? '' }}</div>
|
||||
</div>
|
||||
|
|
@ -117,16 +130,20 @@
|
|||
</div>
|
||||
<div class="text-xs text-zinc-600 dark:text-zinc-400">
|
||||
{{ $item->content['subline'] ?? '' }}
|
||||
@if(!empty($item->content['url']))
|
||||
@if (!empty($item->content['url']))
|
||||
<span class="ml-2">{{ Str::limit($item->content['url'], 40) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:button wire:click="toggleItemStatus({{ $item->id }})" size="sm" variant="ghost" :icon="$item->is_active ? 'eye-slash' : 'eye'"></flux:button>
|
||||
<flux:button wire:click="openItemModal({{ $item->id }})" size="sm" variant="ghost" icon="pencil"></flux:button>
|
||||
<flux:button wire:click="deleteItem({{ $item->id }})" wire:confirm="Möchten Sie diesen Eintrag wirklich löschen?" size="sm" variant="ghost" icon="trash" class="text-red-600 hover:text-red-700"></flux:button>
|
||||
<flux:button wire:click="toggleItemStatus({{ $item->id }})" size="sm" variant="ghost"
|
||||
:icon="$item->is_active ? 'eye-slash' : 'eye'"></flux:button>
|
||||
<flux:button wire:click="openItemModal({{ $item->id }})" size="sm" variant="ghost"
|
||||
icon="pencil"></flux:button>
|
||||
<flux:button wire:click="deleteItem({{ $item->id }})"
|
||||
wire:confirm="Möchten Sie diesen Eintrag wirklich löschen?" size="sm" variant="ghost"
|
||||
icon="trash" class="text-red-600 hover:text-red-700"></flux:button>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
|
|
|||
|
|
@ -35,26 +35,26 @@ mount(function ($hubId = null) {
|
|||
|
||||
// Auto-generate slug from name
|
||||
$updatedName = function ($value) {
|
||||
if (!$this->hubId) { // Only auto-generate for new hubs
|
||||
if (!$this->hubId) {
|
||||
// Only auto-generate for new hubs
|
||||
$this->slug = \Illuminate\Support\Str::slug($value);
|
||||
}
|
||||
};
|
||||
|
||||
$locations = computed(function () {
|
||||
if (!$this->hubId) return collect();
|
||||
if (!$this->hubId) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return \App\Models\HubLocation::where('hub_id', $this->hubId)
|
||||
->when($this->zipSearch, fn($q) => $q->where('zip_code', 'like', "%{$this->zipSearch}%")
|
||||
->orWhere('city_name', 'like', "%{$this->zipSearch}%"))
|
||||
->orderBy('zip_code')
|
||||
->paginate(50);
|
||||
return \App\Models\HubLocation::where('hub_id', $this->hubId)->when($this->zipSearch, fn($q) => $q->where('zip_code', 'like', "%{$this->zipSearch}%")->orWhere('city_name', 'like', "%{$this->zipSearch}%"))->orderBy('zip_code')->paginate(50);
|
||||
});
|
||||
|
||||
$partners = computed(function () {
|
||||
if (!$this->hubId) return collect();
|
||||
if (!$this->hubId) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return \App\Models\Partner::where('hub_id', $this->hubId)
|
||||
->get();
|
||||
return \App\Models\Partner::where('hub_id', $this->hubId)->get();
|
||||
});
|
||||
|
||||
// Dummy save function
|
||||
|
|
@ -103,12 +103,12 @@ $deleteLocation = function ($id) {
|
|||
|
||||
{{-- Flash Message --}}
|
||||
@if (session()->has('message'))
|
||||
<flux:card class="p-4 bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800">
|
||||
<div class="flex items-center gap-3">
|
||||
<flux:icon.check-circle class="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
<span class="text-sm text-green-900 dark:text-green-100">{{ session('message') }}</span>
|
||||
</div>
|
||||
</flux:card>
|
||||
<flux:card class="p-4 bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800">
|
||||
<div class="flex items-center gap-3">
|
||||
<flux:icon.check-circle class="h-5 w-5 text-green-600 dark:text-green-400" />
|
||||
<span class="text-sm text-green-900 dark:text-green-100">{{ session('message') }}</span>
|
||||
</div>
|
||||
</flux:card>
|
||||
@endif
|
||||
|
||||
{{-- Tabs --}}
|
||||
|
|
@ -119,364 +119,360 @@ $deleteLocation = function ($id) {
|
|||
</flux:tabs>
|
||||
|
||||
{{-- TAB 1: Identität & Design --}}
|
||||
@if($activeTab === 'identity')
|
||||
<flux:card class="p-6 space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{{-- Basis-Informationen --}}
|
||||
<div class="space-y-6">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Hub-Name') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model.live="name" placeholder="Ostwestfalen-Lippe" />
|
||||
<flux:description>{{ __('Wird dem Kunden angezeigt, z.B. "Region Ostwestfalen-Lippe"') }}</flux:description>
|
||||
</flux:field>
|
||||
@if ($activeTab === 'identity')
|
||||
<flux:card class="p-6 space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{{-- Basis-Informationen --}}
|
||||
<div class="space-y-6">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Hub-Name') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model.live="name" placeholder="Ostwestfalen-Lippe" />
|
||||
<flux:description>{{ __('Wird dem Kunden angezeigt, z.B. "Region Ostwestfalen-Lippe"') }}
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('URL-Slug') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="slug" placeholder="owl" />
|
||||
<flux:description>{{ __('Für saubere URLs, z.B. b2in.de/region/owl') }}</flux:description>
|
||||
</flux:field>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('URL-Slug') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="slug" placeholder="owl" />
|
||||
<flux:description>{{ __('Für saubere URLs, z.B. b2in.eu/region/owl') }}</flux:description>
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Status') }}</flux:label>
|
||||
<flux:checkbox wire:model="is_active">
|
||||
{{ __('Hub ist aktiv und für Kunden sichtbar') }}
|
||||
</flux:checkbox>
|
||||
<flux:description>
|
||||
{{ __('Inaktive Hubs sind im "Coming Soon"-Modus') }}
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
</div>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Status') }}</flux:label>
|
||||
<flux:checkbox wire:model="is_active">
|
||||
{{ __('Hub ist aktiv und für Kunden sichtbar') }}
|
||||
</flux:checkbox>
|
||||
<flux:description>
|
||||
{{ __('Inaktive Hubs sind im "Coming Soon"-Modus') }}
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
</div>
|
||||
|
||||
{{-- Vorschau --}}
|
||||
<div class="p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg">
|
||||
<h4 class="font-semibold mb-3 text-zinc-900 dark:text-zinc-100">{{ __('Vorschau: Kunden-Landingpage') }}</h4>
|
||||
<div class="relative h-48 rounded-lg overflow-hidden bg-zinc-200 dark:bg-zinc-700 shadow-lg">
|
||||
@if($keyvisual)
|
||||
<img src="{{ $keyvisual }}" class="w-full h-full object-cover" alt="Keyvisual" />
|
||||
@else
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<flux:icon.photo class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-sm text-zinc-500">{{ __('Keyvisual hochladen') }}</p>
|
||||
{{-- Vorschau --}}
|
||||
<div class="p-4 bg-zinc-50 dark:bg-zinc-800 rounded-lg">
|
||||
<h4 class="font-semibold mb-3 text-zinc-900 dark:text-zinc-100">
|
||||
{{ __('Vorschau: Kunden-Landingpage') }}</h4>
|
||||
<div class="relative h-48 rounded-lg overflow-hidden bg-zinc-200 dark:bg-zinc-700 shadow-lg">
|
||||
@if ($keyvisual)
|
||||
<img src="{{ $keyvisual }}" class="w-full h-full object-cover" alt="Keyvisual" />
|
||||
@else
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<flux:icon.photo class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-sm text-zinc-500">{{ __('Keyvisual hochladen') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/80 to-transparent">
|
||||
<h3 class="text-xl font-bold text-white">
|
||||
{{ $name ?: __('Ihr Hub-Name') }}
|
||||
</h3>
|
||||
<p class="text-sm text-zinc-200">
|
||||
{{ __('Die besten Marken Europas, von Händlern aus Ihrer Nachbarschaft') }}
|
||||
</p>
|
||||
</div>
|
||||
@if ($emblem)
|
||||
<div class="absolute top-3 right-3 w-12 h-12 bg-white rounded-full p-2 shadow-lg">
|
||||
<img src="{{ $emblem }}" alt="Emblem" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
<div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-black/80 to-transparent">
|
||||
<h3 class="text-xl font-bold text-white">
|
||||
{{ $name ?: __('Ihr Hub-Name') }}
|
||||
</h3>
|
||||
<p class="text-sm text-zinc-200">
|
||||
{{ __('Die besten Marken Europas, von Händlern aus Ihrer Nachbarschaft') }}
|
||||
</p>
|
||||
</div>
|
||||
@if($emblem)
|
||||
<div class="absolute top-3 right-3 w-12 h-12 bg-white rounded-full p-2 shadow-lg">
|
||||
<img src="{{ $emblem }}" alt="Emblem" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<flux:separator />
|
||||
<flux:separator />
|
||||
|
||||
{{-- Keyvisual Upload --}}
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Keyvisual (Hintergrundbild)') }}</flux:label>
|
||||
{{-- <flux:input type="file" wire:model="keyvisual" accept="image/*" /> --}}
|
||||
<flux:description>
|
||||
{{ __('Atmosphärisches Regionalbild für emotionalen Einstieg') }}
|
||||
<br>
|
||||
<span class="text-xs">
|
||||
{{ __('Beispiele: Hermannsdenkmal (OWL), Skyline (Frankfurt), Hafen (Hamburg)') }}
|
||||
• {{ __('Empfohlen: 1920x800px, max. 2MB') }}
|
||||
</span>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
{{-- Keyvisual Upload --}}
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Keyvisual (Hintergrundbild)') }}</flux:label>
|
||||
{{-- <flux:input type="file" wire:model="keyvisual" accept="image/*" /> --}}
|
||||
<flux:description>
|
||||
{{ __('Atmosphärisches Regionalbild für emotionalen Einstieg') }}
|
||||
<br>
|
||||
<span class="text-xs">
|
||||
{{ __('Beispiele: Hermannsdenkmal (OWL), Skyline (Frankfurt), Hafen (Hamburg)') }}
|
||||
• {{ __('Empfohlen: 1920x800px, max. 2MB') }}
|
||||
</span>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
|
||||
{{-- Wappen Upload --}}
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Wappen / Emblem') }}</flux:label>
|
||||
{{-- <flux:input type="file" wire:model="emblem" accept="image/*" /> --}}
|
||||
<flux:description>
|
||||
{{ __('Offizielles Wappen oder Logo der Region für Vertrauen & Offizialität') }}
|
||||
<br>
|
||||
<span class="text-xs">
|
||||
{{ __('Beispiele: Sparrenburg-Logo, Landeswappen') }}
|
||||
• {{ __('Empfohlen: 256x256px, PNG mit transparentem Hintergrund') }}
|
||||
</span>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
</flux:card>
|
||||
{{-- Wappen Upload --}}
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Wappen / Emblem') }}</flux:label>
|
||||
{{-- <flux:input type="file" wire:model="emblem" accept="image/*" /> --}}
|
||||
<flux:description>
|
||||
{{ __('Offizielles Wappen oder Logo der Region für Vertrauen & Offizialität') }}
|
||||
<br>
|
||||
<span class="text-xs">
|
||||
{{ __('Beispiele: Sparrenburg-Logo, Landeswappen') }}
|
||||
• {{ __('Empfohlen: 256x256px, PNG mit transparentem Hintergrund') }}
|
||||
</span>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
</flux:card>
|
||||
@endif
|
||||
|
||||
{{-- TAB 2: Geografie & PLZ --}}
|
||||
@if($activeTab === 'geography')
|
||||
<div class="space-y-6">
|
||||
@if ($activeTab === 'geography')
|
||||
<div class="space-y-6">
|
||||
|
||||
{{-- Info-Box --}}
|
||||
<flux:card class="p-4 bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800">
|
||||
<div class="flex items-start gap-3">
|
||||
<flux:icon.light-bulb class="h-5 w-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-semibold text-yellow-900 dark:text-yellow-100">
|
||||
{{ __('Die Mapping-Engine') }}
|
||||
</div>
|
||||
<div class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
|
||||
{{ __('Hier ordnen Sie Postleitzahlen diesem Hub zu. Gibt ein Kunde seine PLZ ein, wird er automatisch diesem regionalen Marktplatz zugewiesen und sieht lokale Händler.') }}
|
||||
{{-- Info-Box --}}
|
||||
<flux:card class="p-4 bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800">
|
||||
<div class="flex items-start gap-3">
|
||||
<flux:icon.light-bulb class="h-5 w-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-semibold text-yellow-900 dark:text-yellow-100">
|
||||
{{ __('Die Mapping-Engine') }}
|
||||
</div>
|
||||
<div class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
|
||||
{{ __('Hier ordnen Sie Postleitzahlen diesem Hub zu. Gibt ein Kunde seine PLZ ein, wird er automatisch diesem regionalen Marktplatz zugewiesen und sieht lokale Händler.') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</flux:card>
|
||||
|
||||
{{-- PLZ-Import Tools --}}
|
||||
<flux:card class="p-6">
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Postleitzahlen hinzufügen') }}</flux:heading>
|
||||
{{-- PLZ-Import Tools --}}
|
||||
<flux:card class="p-6">
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Postleitzahlen hinzufügen') }}</flux:heading>
|
||||
|
||||
<flux:tabs wire:model.live="importMethod" variant="segmented">
|
||||
<flux:tab name="single" icon="plus">{{ __('Einzeln') }}</flux:tab>
|
||||
<flux:tab name="range" icon="arrows-right-left">{{ __('Bereich') }}</flux:tab>
|
||||
<flux:tab name="csv" icon="document">{{ __('CSV-Import') }}</flux:tab>
|
||||
</flux:tabs>
|
||||
<flux:tabs wire:model.live="importMethod" variant="segmented">
|
||||
<flux:tab name="single" icon="plus">{{ __('Einzeln') }}</flux:tab>
|
||||
<flux:tab name="range" icon="arrows-right-left">{{ __('Bereich') }}</flux:tab>
|
||||
<flux:tab name="csv" icon="document">{{ __('CSV-Import') }}</flux:tab>
|
||||
</flux:tabs>
|
||||
|
||||
<div class="mt-6">
|
||||
{{-- Einzelne PLZ --}}
|
||||
@if($importMethod === 'single')
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Postleitzahl') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="newZipCode" placeholder="33602" />
|
||||
</flux:field>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Stadt') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="newCityName" placeholder="Bielefeld" />
|
||||
</flux:field>
|
||||
<div class="mt-6">
|
||||
{{-- Einzelne PLZ --}}
|
||||
@if ($importMethod === 'single')
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Postleitzahl') }} <span class="text-red-500">*</span>
|
||||
</flux:label>
|
||||
<flux:input wire:model="newZipCode" placeholder="33602" />
|
||||
</flux:field>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Stadt') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="newCityName" placeholder="Bielefeld" />
|
||||
</flux:field>
|
||||
</div>
|
||||
<flux:button wire:click="addSingleZip" icon="plus">
|
||||
{{ __('PLZ hinzufügen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- PLZ-Bereich --}}
|
||||
@if ($importMethod === 'range')
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Von PLZ') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="rangeStart" placeholder="33000" />
|
||||
</flux:field>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Bis PLZ') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="rangeEnd" placeholder="33999" />
|
||||
</flux:field>
|
||||
</div>
|
||||
<flux:button wire:click="addZipRange" icon="arrows-right-left">
|
||||
{{ __('Bereich importieren') }}
|
||||
</flux:button>
|
||||
<flux:description>
|
||||
⚠️
|
||||
{{ __('Generiert automatisch alle PLZs im Bereich. Kann bei großen Bereichen mehrere Minuten dauern!') }}
|
||||
</flux:description>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- CSV-Import --}}
|
||||
@if ($importMethod === 'csv')
|
||||
<div class="space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('CSV-Datei') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input type="file" wire:model="csvFile" accept=".csv" />
|
||||
<flux:description>
|
||||
{{ __('Format: PLZ,Stadt (eine Zeile pro Eintrag)') }}
|
||||
<br>
|
||||
<span class="text-xs">{{ __('Beispiel:') }}</span>
|
||||
<code class="text-xs bg-zinc-100 dark:bg-zinc-800 px-2 py-1 rounded">
|
||||
33602,Bielefeld<br>33603,Bielefeld<br>33604,Bielefeld
|
||||
</code>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
<flux:button wire:click="importCsv" icon="arrow-up-tray">
|
||||
{{ __('CSV importieren') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
{{-- PLZ-Liste --}}
|
||||
<flux:card>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<flux:heading size="lg">
|
||||
{{ __('Zugeordnete Postleitzahlen') }}
|
||||
@if ($hubId)
|
||||
<span class="text-sm font-normal text-zinc-600 dark:text-zinc-400">
|
||||
({{ $this->locations->total() }} {{ __('gesamt') }})
|
||||
</span>
|
||||
@endif
|
||||
</flux:heading>
|
||||
<flux:input wire:model.live.debounce="zipSearch"
|
||||
placeholder="{{ __('PLZ oder Stadt suchen...') }}" icon="magnifying-glass"
|
||||
class="w-64" />
|
||||
</div>
|
||||
<flux:button wire:click="addSingleZip" icon="plus">
|
||||
{{ __('PLZ hinzufügen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- PLZ-Bereich --}}
|
||||
@if($importMethod === 'range')
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Von PLZ') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="rangeStart" placeholder="33000" />
|
||||
</flux:field>
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Bis PLZ') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input wire:model="rangeEnd" placeholder="33999" />
|
||||
</flux:field>
|
||||
</div>
|
||||
<flux:button wire:click="addZipRange" icon="arrows-right-left">
|
||||
{{ __('Bereich importieren') }}
|
||||
</flux:button>
|
||||
<flux:description>
|
||||
⚠️ {{ __('Generiert automatisch alle PLZs im Bereich. Kann bei großen Bereichen mehrere Minuten dauern!') }}
|
||||
</flux:description>
|
||||
</div>
|
||||
@endif
|
||||
@if ($hubId)
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('PLZ') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Stadt') }}</flux:table.column>
|
||||
<flux:table.column class="text-right w-32">{{ __('Aktion') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
{{-- CSV-Import --}}
|
||||
@if($importMethod === 'csv')
|
||||
<div class="space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('CSV-Datei') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:input type="file" wire:model="csvFile" accept=".csv" />
|
||||
<flux:description>
|
||||
{{ __('Format: PLZ,Stadt (eine Zeile pro Eintrag)') }}
|
||||
<br>
|
||||
<span class="text-xs">{{ __('Beispiel:') }}</span>
|
||||
<code class="text-xs bg-zinc-100 dark:bg-zinc-800 px-2 py-1 rounded">
|
||||
33602,Bielefeld<br>33603,Bielefeld<br>33604,Bielefeld
|
||||
</code>
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
<flux:button wire:click="importCsv" icon="arrow-up-tray">
|
||||
{{ __('CSV importieren') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</flux:card>
|
||||
<flux:table.rows>
|
||||
@forelse($this->locations as $location)
|
||||
<flux:table.row :key="$location->id">
|
||||
<flux:table.cell>
|
||||
<span class="font-mono">{{ $location->zip_code }}</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>{{ $location->city_name }}</flux:table.cell>
|
||||
<flux:table.cell class="text-right">
|
||||
<flux:button variant="ghost" size="sm" icon="trash"
|
||||
wire:click="deleteLocation({{ $location->id }})" />
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="3" class="text-center py-8">
|
||||
<flux:icon.map-pin class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Noch keine PLZs zugeordnet') }}
|
||||
</p>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
|
||||
{{-- PLZ-Liste --}}
|
||||
<flux:card>
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<flux:heading size="lg">
|
||||
{{ __('Zugeordnete Postleitzahlen') }}
|
||||
@if($hubId)
|
||||
<span class="text-sm font-normal text-zinc-600 dark:text-zinc-400">
|
||||
({{ $this->locations->total() }} {{ __('gesamt') }})
|
||||
</span>
|
||||
{{-- Pagination --}}
|
||||
@if ($this->locations->hasPages())
|
||||
<div class="mt-4 border-t border-zinc-200 dark:border-zinc-700 pt-4">
|
||||
{{ $this->locations->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</flux:heading>
|
||||
<flux:input
|
||||
wire:model.live.debounce="zipSearch"
|
||||
placeholder="{{ __('PLZ oder Stadt suchen...') }}"
|
||||
icon="magnifying-glass"
|
||||
class="w-64"
|
||||
/>
|
||||
@else
|
||||
<div class="text-center py-8 text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Speichern Sie zuerst den Hub, um PLZs hinzuzufügen') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if($hubId)
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('PLZ') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Stadt') }}</flux:table.column>
|
||||
<flux:table.column class="text-right w-32">{{ __('Aktion') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
<flux:table.rows>
|
||||
@forelse($this->locations as $location)
|
||||
<flux:table.row :key="$location->id">
|
||||
<flux:table.cell>
|
||||
<span class="font-mono">{{ $location->zip_code }}</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>{{ $location->city_name }}</flux:table.cell>
|
||||
<flux:table.cell class="text-right">
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
icon="trash"
|
||||
wire:click="deleteLocation({{ $location->id }})"
|
||||
/>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="3" class="text-center py-8">
|
||||
<flux:icon.map-pin class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Noch keine PLZs zugeordnet') }}
|
||||
</p>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
|
||||
{{-- Pagination --}}
|
||||
@if($this->locations->hasPages())
|
||||
<div class="mt-4 border-t border-zinc-200 dark:border-zinc-700 pt-4">
|
||||
{{ $this->locations->links() }}
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="text-center py-8 text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Speichern Sie zuerst den Hub, um PLZs hinzuzufügen') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- TAB 3: Partner-Monitor --}}
|
||||
@if($activeTab === 'partners')
|
||||
<flux:card class="p-6">
|
||||
<flux:heading size="lg" class="mb-4">
|
||||
{{ __('Partner in diesem Hub') }}
|
||||
@if($hubId)
|
||||
<span class="text-sm font-normal text-zinc-600 dark:text-zinc-400">
|
||||
({{ $this->partners->count() }} {{ __('Partner') }})
|
||||
</span>
|
||||
@if ($activeTab === 'partners')
|
||||
<flux:card class="p-6">
|
||||
<flux:heading size="lg" class="mb-4">
|
||||
{{ __('Partner in diesem Hub') }}
|
||||
@if ($hubId)
|
||||
<span class="text-sm font-normal text-zinc-600 dark:text-zinc-400">
|
||||
({{ $this->partners->count() }} {{ __('Partner') }})
|
||||
</span>
|
||||
@endif
|
||||
</flux:heading>
|
||||
|
||||
{{-- Info --}}
|
||||
<flux:card class="p-4 mb-4 bg-zinc-50 dark:bg-zinc-800 border-0">
|
||||
<div class="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<strong>{{ __('Logik:') }}</strong>
|
||||
<ul class="list-disc ml-5 mt-2 space-y-1">
|
||||
<li>{{ __('Händler (Retailer) → Werden diesem Hub fest zugeordnet') }}</li>
|
||||
<li>{{ __('Hersteller (Manufacturer) → Sind "Hub-agnostisch", in allen Hubs sichtbar') }}</li>
|
||||
<li>{{ __('Makler (Estate-Agent) → Werden Hub zugeordnet für regionale Kundenakquise') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
@if ($hubId)
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Name') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Typ') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Stadt') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Lieferradius') }}</flux:table.column>
|
||||
<flux:table.column class="text-center">{{ __('Status') }}</flux:table.column>
|
||||
<flux:table.column class="text-right">{{ __('Aktion') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
<flux:table.rows>
|
||||
@forelse($this->partners as $partner)
|
||||
<flux:table.row :key="$partner->id">
|
||||
<flux:table.cell>
|
||||
<div class="font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $partner->company_name }}
|
||||
</div>
|
||||
@if ($partner->display_name && $partner->display_name !== $partner->company_name)
|
||||
<div class="text-xs text-zinc-500">{{ $partner->display_name }}</div>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@php
|
||||
$typeColors = [
|
||||
'Retailer' => 'blue',
|
||||
'Manufacturer' => 'purple',
|
||||
'Estate-Agent' => 'green',
|
||||
];
|
||||
@endphp
|
||||
<flux:badge :color="$typeColors[$partner->type] ?? 'zinc'" size="sm">
|
||||
{{ $partner->type }}
|
||||
</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
{{ $partner->city ?? '-' }}
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if ($partner->delivery_radius_km)
|
||||
<span class="text-sm">{{ $partner->delivery_radius_km }} km</span>
|
||||
@else
|
||||
<span class="text-sm text-zinc-400">-</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell class="text-center">
|
||||
<flux:badge :color="$partner->is_active ? 'green' : 'zinc'" size="sm">
|
||||
{{ $partner->is_active ? __('Aktiv') : __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell class="text-right">
|
||||
<flux:button variant="ghost" size="sm" icon="eye">
|
||||
{{ __('Details') }}
|
||||
</flux:button>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="6" class="text-center py-8">
|
||||
<flux:icon.user-group class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Noch keine Partner in diesem Hub') }}
|
||||
</p>
|
||||
<p class="text-xs text-zinc-500 mt-2">
|
||||
{{ __('Partner werden automatisch beim Onboarding zugeordnet (basierend auf PLZ)') }}
|
||||
</p>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
@else
|
||||
<div class="text-center py-8 text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Speichern Sie zuerst den Hub, um Partner-Zuordnungen zu sehen') }}
|
||||
</div>
|
||||
@endif
|
||||
</flux:heading>
|
||||
|
||||
{{-- Info --}}
|
||||
<flux:card class="p-4 mb-4 bg-zinc-50 dark:bg-zinc-800 border-0">
|
||||
<div class="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<strong>{{ __('Logik:') }}</strong>
|
||||
<ul class="list-disc ml-5 mt-2 space-y-1">
|
||||
<li>{{ __('Händler (Retailer) → Werden diesem Hub fest zugeordnet') }}</li>
|
||||
<li>{{ __('Hersteller (Manufacturer) → Sind "Hub-agnostisch", in allen Hubs sichtbar') }}</li>
|
||||
<li>{{ __('Makler (Estate-Agent) → Werden Hub zugeordnet für regionale Kundenakquise') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
@if($hubId)
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Name') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Typ') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Stadt') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Lieferradius') }}</flux:table.column>
|
||||
<flux:table.column class="text-center">{{ __('Status') }}</flux:table.column>
|
||||
<flux:table.column class="text-right">{{ __('Aktion') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
<flux:table.rows>
|
||||
@forelse($this->partners as $partner)
|
||||
<flux:table.row :key="$partner->id">
|
||||
<flux:table.cell>
|
||||
<div class="font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{{ $partner->company_name }}
|
||||
</div>
|
||||
@if($partner->display_name && $partner->display_name !== $partner->company_name)
|
||||
<div class="text-xs text-zinc-500">{{ $partner->display_name }}</div>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@php
|
||||
$typeColors = [
|
||||
'Retailer' => 'blue',
|
||||
'Manufacturer' => 'purple',
|
||||
'Estate-Agent' => 'green',
|
||||
];
|
||||
@endphp
|
||||
<flux:badge :color="$typeColors[$partner->type] ?? 'zinc'" size="sm">
|
||||
{{ $partner->type }}
|
||||
</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
{{ $partner->city ?? '-' }}
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if($partner->delivery_radius_km)
|
||||
<span class="text-sm">{{ $partner->delivery_radius_km }} km</span>
|
||||
@else
|
||||
<span class="text-sm text-zinc-400">-</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell class="text-center">
|
||||
<flux:badge :color="$partner->is_active ? 'green' : 'zinc'" size="sm">
|
||||
{{ $partner->is_active ? __('Aktiv') : __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell class="text-right">
|
||||
<flux:button variant="ghost" size="sm" icon="eye">
|
||||
{{ __('Details') }}
|
||||
</flux:button>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="6" class="text-center py-8">
|
||||
<flux:icon.user-group class="w-12 h-12 text-zinc-400 mx-auto mb-2" />
|
||||
<p class="text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Noch keine Partner in diesem Hub') }}
|
||||
</p>
|
||||
<p class="text-xs text-zinc-500 mt-2">
|
||||
{{ __('Partner werden automatisch beim Onboarding zugeordnet (basierend auf PLZ)') }}
|
||||
</p>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
@else
|
||||
<div class="text-center py-8 text-zinc-600 dark:text-zinc-400">
|
||||
{{ __('Speichern Sie zuerst den Hub, um Partner-Zuordnungen zu sehen') }}
|
||||
</div>
|
||||
@endif
|
||||
</flux:card>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue