307 lines
17 KiB
PHP
307 lines
17 KiB
PHP
<div>
|
|
<flux:header class="mb-6">
|
|
<flux:heading size="xl">{{ __('Cabinet Display - CMS Verwaltung') }}</flux:heading>
|
|
<flux:subheading>{{ __('Verwalten Sie die Inhalte der Display-Seite') }}</flux:subheading>
|
|
</flux:header>
|
|
|
|
{{-- Hilfe-Banner --}}
|
|
<flux:card class="mb-6 bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800">
|
|
<div class="flex items-start gap-4">
|
|
<flux:icon.information-circle class="w-6 h-6 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
|
<div class="flex-1">
|
|
<h3 class="font-semibold text-blue-900 dark:text-blue-100 mb-2">{{ __('Schnellanleitung') }}</h3>
|
|
<div class="text-sm text-blue-800 dark:text-blue-200 space-y-1">
|
|
<p>• <strong>Videos:</strong> Videos müssen aufgrund der Dateigrößer vorab per SFTP hochgeladen werden. Die Position (0-100%) bestimmt den vertikalen Bildausschnitt.</p>
|
|
<p>• <strong>Footer-Inhalte:</strong> Werden alle 30 Sekunden gewechselt. URLs werden automatisch als QR-Code angezeigt.</p>
|
|
<p>• <strong>Footer-Inhalte:</strong> Sind alle Inhalte ausgeblendet, wird der Footer ausgeblendet und das Video auf 100% der Höhe angezeigt.</p>
|
|
<p>• <strong>Display-URL:</strong> <code class="px-2 py-0.5 bg-blue-100 dark:bg-blue-800 rounded">https://cabinet.b2in.eu</code></p>
|
|
<p>• <strong>API-Endpunkt:</strong> <code class="px-2 py-0.5 bg-blue-100 dark:bg-blue-800 rounded">{{ url('/api/display/config') }}</code></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</flux:card>
|
|
|
|
{{-- Success-Meldungen --}}
|
|
@if (session()->has('success'))
|
|
<x-success-alert>
|
|
{{ session('success') }}
|
|
</x-success-alert>
|
|
@endif
|
|
|
|
{{-- Video-Verwaltung --}}
|
|
<flux:card class="mb-8">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<flux:heading size="lg">{{ __('Video-Playlist') }}</flux:heading>
|
|
<flux:subheading>{{ __('Videos werden in der angegebenen Reihenfolge abgespielt') }}</flux:subheading>
|
|
</div>
|
|
<flux:button wire:click="openVideoModal" icon="plus">
|
|
{{ __('Video hinzufügen') }}
|
|
</flux:button>
|
|
</div>
|
|
|
|
@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. Fügen Sie Ihr erstes Video hinzu!') }}</p>
|
|
</div>
|
|
@else
|
|
<div class="space-y-3">
|
|
@foreach($videos as $index => $video)
|
|
<div wire:key="video-{{ $video->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 hover:border-zinc-300 dark:hover:border-zinc-600 transition">
|
|
|
|
<div class="flex flex-col gap-1">
|
|
@if($index > 0)
|
|
<flux:button wire:click="moveVideo({{ $video->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="moveVideo({{ $video->id }}, 'down')"
|
|
size="xs"
|
|
variant="ghost"
|
|
icon="chevron-down"
|
|
class="text-zinc-400 hover:text-zinc-600">
|
|
</flux:button>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center gap-3 mb-1">
|
|
<flux:badge :color="$video->is_active ? 'green' : 'zinc'" size="sm">
|
|
{{ $video->is_active ? __('Aktiv') : __('Inaktiv') }}
|
|
</flux:badge>
|
|
<span class="font-semibold text-sm">{{ $video->title ?: $video->filename }}</span>
|
|
</div>
|
|
<div class="text-xs text-zinc-600 dark:text-zinc-400 space-x-4">
|
|
<span>📁 {{ $video->filename }}</span>
|
|
<span>📍 Position: {{ $video->position }}%</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<flux:button wire:click="toggleVideoStatus({{ $video->id }})"
|
|
size="sm"
|
|
variant="ghost"
|
|
:icon="$video->is_active ? 'eye-slash' : 'eye'">
|
|
</flux:button>
|
|
|
|
<flux:button wire:click="openVideoModal({{ $video->id }})"
|
|
size="sm"
|
|
variant="ghost"
|
|
icon="pencil">
|
|
</flux:button>
|
|
|
|
<flux:button wire:click="deleteVideo({{ $video->id }})"
|
|
wire:confirm="Möchten Sie dieses Video wirklich löschen?"
|
|
size="sm"
|
|
variant="ghost"
|
|
icon="trash"
|
|
class="text-red-600 hover:text-red-700">
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</flux:card>
|
|
|
|
{{-- Footer-Content-Verwaltung --}}
|
|
<flux:card>
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<flux:heading size="lg">{{ __('Footer-Inhalte') }}</flux:heading>
|
|
<flux:subheading>{{ __('Inhalte werden alle 30 Sekunden im Footer gewechselt') }}</flux:subheading>
|
|
@if($footerContents->isNotEmpty())
|
|
<div class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
|
|
📊 Gesamt-Klicks: <strong>{{ $footerContents->sum('clicks') }}</strong>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<flux:button wire:click="openFooterModal" icon="plus">
|
|
{{ __('Inhalt hinzufügen') }}
|
|
</flux:button>
|
|
</div>
|
|
|
|
@if($footerContents->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. Fügen Sie den ersten Inhalt hinzu!') }}</p>
|
|
</div>
|
|
@else
|
|
<div class="space-y-3">
|
|
@foreach($footerContents as $index => $footer)
|
|
<div wire:key="footer-{{ $footer->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 hover:border-zinc-300 dark:hover:border-zinc-600 transition">
|
|
|
|
<div class="flex flex-col gap-1">
|
|
@if($index > 0)
|
|
<flux:button wire:click="moveFooter({{ $footer->id }}, 'up')"
|
|
size="xs"
|
|
variant="ghost"
|
|
icon="chevron-up"
|
|
class="text-zinc-400 hover:text-zinc-600">
|
|
</flux:button>
|
|
@endif
|
|
@if($index < count($footerContents) - 1)
|
|
<flux:button wire:click="moveFooter({{ $footer->id }}, 'down')"
|
|
size="xs"
|
|
variant="ghost"
|
|
icon="chevron-down"
|
|
class="text-zinc-400 hover:text-zinc-600">
|
|
</flux:button>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center gap-3 mb-1">
|
|
<flux:badge :color="$footer->is_active ? 'green' : 'zinc'" size="sm">
|
|
{{ $footer->is_active ? __('Aktiv') : __('Inaktiv') }}
|
|
</flux:badge>
|
|
<span class="font-semibold text-sm">{{ $footer->headline }}</span>
|
|
<flux:badge color="blue" size="sm">
|
|
<flux:icon.cursor-arrow-rays class="w-3 h-3" />
|
|
{{ $footer->clicks }} {{ __('Klicks') }}
|
|
</flux:badge>
|
|
</div>
|
|
<div class="text-xs text-zinc-600 dark:text-zinc-400 space-y-1">
|
|
<div>{{ $footer->subline }}</div>
|
|
|
|
@if($footer->url)
|
|
<div class="flex items-center gap-1">
|
|
<flux:icon.link class="w-3 h-3" />
|
|
<span class="font-mono bg-zinc-100 dark:bg-zinc-700 px-2 py-0.5 rounded">
|
|
{{ $footer->short_code }}
|
|
</span>
|
|
<button
|
|
onclick="navigator.clipboard.writeText('{{ $footer->short_url }}'); alert('Short-Link kopiert!');"
|
|
class="text-blue-600 hover:text-blue-700 dark:text-blue-400 text-xs"
|
|
title="Short-Link kopieren">
|
|
📋 Short-Link
|
|
</button>
|
|
</div>
|
|
<div class="flex items-center gap-1 text-xs">
|
|
<flux:icon.arrow-top-right-on-square class="w-3 h-3" />
|
|
<a href="{{ $footer->url }}" target="_blank" class="hover:underline">
|
|
{{ Str::limit($footer->url, 50) }}
|
|
</a>
|
|
</div>
|
|
@else
|
|
<div class="flex items-center gap-1 text-xs text-zinc-500 dark:text-zinc-500">
|
|
<flux:icon.x-circle class="w-3 h-3" />
|
|
<span>Kein QR-Code (Keine URL angegeben)</span>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<flux:button wire:click="toggleFooterStatus({{ $footer->id }})"
|
|
size="sm"
|
|
variant="ghost"
|
|
:icon="$footer->is_active ? 'eye-slash' : 'eye'"
|
|
title="{{ $footer->is_active ? 'Deaktivieren' : 'Aktivieren' }}">
|
|
</flux:button>
|
|
|
|
<flux:dropdown>
|
|
<flux:button size="sm" variant="ghost" icon="ellipsis-vertical"></flux:button>
|
|
<flux:menu>
|
|
<flux:menu.item wire:click="openFooterModal({{ $footer->id }})" icon="pencil">
|
|
{{ __('Bearbeiten') }}
|
|
</flux:menu.item>
|
|
<flux:menu.item wire:click="regenerateShortCode({{ $footer->id }})" icon="arrow-path">
|
|
{{ __('Short-Code neu generieren') }}
|
|
</flux:menu.item>
|
|
<flux:menu.item wire:click="resetClicks({{ $footer->id }})"
|
|
wire:confirm="Möchten Sie den Klick-Zähler wirklich zurücksetzen?"
|
|
icon="arrow-path-rounded-square">
|
|
{{ __('Klicks zurücksetzen') }}
|
|
</flux:menu.item>
|
|
<flux:menu.separator />
|
|
<flux:menu.item wire:click="deleteFooter({{ $footer->id }})"
|
|
wire:confirm="Möchten Sie diesen Footer-Inhalt wirklich löschen?"
|
|
icon="trash"
|
|
class="text-red-600">
|
|
{{ __('Löschen') }}
|
|
</flux:menu.item>
|
|
</flux:menu>
|
|
</flux:dropdown>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</flux:card>
|
|
|
|
{{-- Video Modal --}}
|
|
<flux:modal :open="$showVideoModal" wire:model="showVideoModal">
|
|
<form wire:submit.prevent="saveVideo">
|
|
<div class="space-y-6">
|
|
<div>
|
|
<flux:heading size="lg">{{ $videoId ? __('Video bearbeiten') : __('Video hinzufügen') }}</flux:heading>
|
|
</div>
|
|
|
|
<flux:select wire:model="videoFilename" label="Video-Datei" placeholder="Wählen Sie ein Video...">
|
|
@foreach($availableVideos as $videoFile)
|
|
<option value="{{ $videoFile }}">{{ $videoFile }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
@error('videoFilename') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
|
|
<flux:input wire:model="videoTitle" label="Titel (optional)" placeholder="z.B. Herbst Kollektion 2025" />
|
|
|
|
<flux:input wire:model="videoPosition" type="number" min="0" max="100" label="Position (%)"
|
|
description="Vertikale Position im Video (0 = oben, 100 = unten)" />
|
|
@error('videoPosition') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
|
|
<flux:checkbox wire:model="videoIsActive" label="Video aktiv anzeigen" />
|
|
|
|
<div class="flex justify-end gap-3 pt-4">
|
|
<flux:button type="button" wire:click="closeVideoModal" variant="ghost">
|
|
{{ __('Abbrechen') }}
|
|
</flux:button>
|
|
<flux:button type="submit" variant="primary">
|
|
{{ $videoId ? __('Aktualisieren') : __('Hinzufügen') }}
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
|
|
{{-- Footer Modal --}}
|
|
<flux:modal :open="$showFooterModal" wire:model="showFooterModal">
|
|
<form wire:submit.prevent="saveFooter">
|
|
<div class="space-y-6">
|
|
<div>
|
|
<flux:heading size="lg">{{ $footerId ? __('Footer-Inhalt bearbeiten') : __('Footer-Inhalt hinzufügen') }}</flux:heading>
|
|
</div>
|
|
|
|
<flux:input wire:model="footerHeadline" label="Überschrift" placeholder="z.B. Beratung & Termin" />
|
|
@error('footerHeadline') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
|
|
<flux:input wire:model="footerSubline" label="Unterzeile" placeholder="z.B. Jetzt Termin vereinbaren." />
|
|
@error('footerSubline') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
|
|
<flux:input wire:model="footerUrl" label="URL (optional)" placeholder="https://www.cabinet.de/bielefeld..."
|
|
description="Leer lassen = Kein QR-Code wird angezeigt. Mit URL = QR-Code mit Short-Link wird generiert." />
|
|
@error('footerUrl') <span class="text-red-600 text-sm">{{ $message }}</span> @enderror
|
|
|
|
<flux:checkbox wire:model="footerIsActive" label="Footer-Inhalt aktiv anzeigen" />
|
|
|
|
<div class="flex justify-end gap-3 pt-4">
|
|
<flux:button type="button" wire:click="closeFooterModal" variant="ghost">
|
|
{{ __('Abbrechen') }}
|
|
</flux:button>
|
|
<flux:button type="submit" variant="primary">
|
|
{{ $footerId ? __('Aktualisieren') : __('Hinzufügen') }}
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</flux:modal>
|
|
</div>
|
|
|