presseportale/resources/views/livewire/admin/footer-codes/index.blade.php
Kevin Adametz 2cde7284a1 livewire: Volt entfernt — Komponenten als Livewire-4-Single-File-Components, Route::livewire(), Livewire::test(); livewire/volt deinstalliert
- 83 Komponenten: Livewire\Volt\Component → Livewire\Component
- 73 Routen (admin, auth, customer): Volt::route() → Route::livewire()
- 88 Testdateien: Volt::test/actingAs → Livewire::test/actingAs
- VoltServiceProvider gelöscht; composer remove livewire/volt
- config/livewire.php auf v4-Schlüssel (component_layout, component_placeholder,
  component_locations, make_command: SFC ohne ⚡). Der alte Schlüssel layout war
  wirkungslos; alle gerouteten Komponenten setzen ihr Layout selbst.
- CLAUDE.md, AGENTS.md, boost.json, .cursorrules, Skill volt-development
- Roadmap: erledigt; Larastan erfasst SFCs weiterhin nicht (Blade-Dateien)

Suite 2.325 grün, einer übersprungen; Larastan und Pint ohne Meldung;
route:cache und view:cache laufen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 13:59:36 +00:00

240 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Enums\Portal;
use App\Models\FooterCode;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;
use Livewire\WithPagination;
new #[Layout('components.layouts.app'), Title('Footer-Codes')] class extends Component
{
use WithPagination;
public string $search = '';
public string $portalFilter = 'all';
public string $statusFilter = 'all';
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedPortalFilter(): void
{
$this->resetPage();
}
public function updatedStatusFilter(): void
{
$this->resetPage();
}
public function toggleActive(int $id): void
{
$this->authorize('footer-codes:manage');
$footerCode = FooterCode::query()->find($id);
if (! $footerCode) {
return;
}
$footerCode->update(['is_active' => ! $footerCode->is_active]);
}
public function with(): array
{
$term = trim($this->search);
$codes = FooterCode::query()
->withCount('categories')
->when($this->portalFilter !== 'all', fn ($q) => $q->where('portal', $this->portalFilter))
->when($this->statusFilter === 'active', fn ($q) => $q->where('is_active', true))
->when($this->statusFilter === 'inactive', fn ($q) => $q->where('is_active', false))
->when($this->statusFilter === 'global', fn ($q) => $q->where('is_global', true))
->when(filled($term), function ($q) use ($term): void {
$q->where(function ($q) use ($term): void {
$q->where('title', 'like', '%'.$term.'%')
->orWhere('content', 'like', '%'.$term.'%');
});
})
->orderByDesc('is_global')
->orderBy('priority')
->orderBy('title')
->paginate(25);
return [
'codes' => $codes,
'portalOptions' => Portal::cases(),
'totals' => [
'total' => FooterCode::query()->count(),
'active' => FooterCode::query()->where('is_active', true)->count(),
'global' => FooterCode::query()->where('is_global', true)->count(),
],
];
}
}; ?>
<div class="space-y-8">
{{-- ============== PAGE HEADER ============== --}}
<x-portal.page-header
:badge="__('Admin Backend')"
:eyebrow="__('Administration · Operations')"
:title="__('Footer-Codes')"
:description="__('Snippets, die unter Pressemitteilungen ausgespielt werden.')"
>
<x-slot:actions>
<flux:button variant="primary" icon="plus" :href="route('admin.footer-codes.create')" wire:navigate>
{{ __('Footer-Code anlegen') }}
</flux:button>
</x-slot:actions>
</x-portal.page-header>
@if (session('success'))
<x-portal.alert variant="success">{{ session('success') }}</x-portal.alert>
@endif
@if (session('error'))
<x-portal.alert variant="error">{{ session('error') }}</x-portal.alert>
@endif
{{-- ============== KPI-Reihe ============== --}}
<section class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="number_format($totals['total'])">
<x-slot:meta>{{ __('Snippets') }}</x-slot:meta>
<x-slot:trend>{{ __('alle Portale') }}</x-slot:trend>
</x-portal.stat-card>
<x-portal.stat-card variant="ok" :label="__('Aktiv')" :value="number_format($totals['active'])">
<x-slot:meta>{{ __('live ausgespielt') }}</x-slot:meta>
<x-slot:trend>{{ __('in PMs sichtbar') }}</x-slot:trend>
</x-portal.stat-card>
<x-portal.stat-card variant="muted" :label="__('Global')" :value="number_format($totals['global'])">
<x-slot:meta>{{ __('portalübergreifend') }}</x-slot:meta>
<x-slot:trend>{{ __('ohne Kategorie-Bindung') }}</x-slot:trend>
</x-portal.stat-card>
</section>
{{-- ============== FILTER ============== --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
</div>
<div class="p-5 grid gap-3 md:grid-cols-[1fr,auto,auto]">
<flux:input
wire:model.live.debounce.300ms="search"
placeholder="{{ __('Titel oder Inhalt suchen…') }}"
icon="magnifying-glass"
/>
<flux:select wire:model.live="portalFilter">
<option value="all">{{ __('Alle Portale') }}</option>
@foreach ($portalOptions as $option)
<option value="{{ $option->value }}">{{ $option->label() }}</option>
@endforeach
</flux:select>
<flux:select wire:model.live="statusFilter">
<option value="all">{{ __('Alle Status') }}</option>
<option value="active">{{ __('Aktiv') }}</option>
<option value="inactive">{{ __('Inaktiv') }}</option>
<option value="global">{{ __('Global') }}</option>
</flux:select>
</div>
</article>
{{-- ============== TABELLE ============== --}}
<article class="panel overflow-hidden">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Alle Footer-Codes') }}</span>
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
{{ __(':count Einträge', ['count' => $codes->total()]) }}
</span>
</div>
<flux:table>
<flux:table.columns>
<flux:table.column>{{ __('Titel') }}</flux:table.column>
<flux:table.column>{{ __('Portal') }}</flux:table.column>
<flux:table.column>{{ __('Sprache') }}</flux:table.column>
<flux:table.column>{{ __('Kategorien') }}</flux:table.column>
<flux:table.column>{{ __('Priorität') }}</flux:table.column>
<flux:table.column>{{ __('Status') }}</flux:table.column>
<flux:table.column align="end">{{ __('Aktionen') }}</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@forelse ($codes as $code)
<flux:table.row :key="'fc-'.$code->id">
<flux:table.cell>
<div class="flex items-center gap-2">
@if ($code->is_global)
<span class="badge hub dot">{{ __('Global') }}</span>
@endif
<span class="text-[13px] font-semibold text-[color:var(--color-ink)]">{{ $code->title }}</span>
</div>
</flux:table.cell>
<flux:table.cell>
<span class="badge hub">{{ $code->portal->label() }}</span>
</flux:table.cell>
<flux:table.cell>
<span class="text-[12px] text-[color:var(--color-ink-2)] font-mono">
{{ $code->language ? strtoupper($code->language) : '–' }}
</span>
</flux:table.cell>
<flux:table.cell>
@if ($code->is_global)
<span class="text-[12px] text-[color:var(--color-ink-3)]">{{ __('alle') }}</span>
@else
<span class="text-[12.5px] text-[color:var(--color-ink)] tabular-nums">{{ $code->categories_count }}</span>
@endif
</flux:table.cell>
<flux:table.cell>
<span class="text-[12.5px] text-[color:var(--color-ink)] tabular-nums">{{ $code->priority }}</span>
</flux:table.cell>
<flux:table.cell>
@if ($code->is_active)
<span class="badge ok dot">{{ __('Aktiv') }}</span>
@else
<span class="badge dot">{{ __('Inaktiv') }}</span>
@endif
</flux:table.cell>
<flux:table.cell align="end">
<div class="flex items-center justify-end gap-1">
<flux:button
size="xs"
variant="filled"
:icon="$code->is_active ? 'pause' : 'play'"
wire:click="toggleActive({{ $code->id }})"
:title="$code->is_active ? __('Deaktivieren') : __('Aktivieren')"
/>
<flux:button
size="xs"
variant="filled"
icon="pencil"
:href="route('admin.footer-codes.edit', $code->id)"
wire:navigate
:title="__('Bearbeiten')"
/>
</div>
</flux:table.cell>
</flux:table.row>
@empty
<flux:table.row>
<flux:table.cell :colspan="7">
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-3
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
<flux:icon.code-bracket-square class="size-6" />
</div>
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
{{ __('Keine Footer-Codes gefunden.') }}
</div>
</div>
</flux:table.cell>
</flux:table.row>
@endforelse
</flux:table.rows>
</flux:table>
<x-portal.table-pagination :paginator="$codes" />
</article>
</div>