User Panel: Phase-8-Abschluss, Titelbild/Lizenzen/Zeitzonen und KI-Pruef-Pipeline
Phase 8 (Rest) + Umbauten vom 10./11.06.: - Ein Titelbild pro PM (Cover 1280x580), SVG-Platzhalter-Set + Picker, PressReleaseCoverImage-Resolver - Lizenz-/Rechteformular nach "Lizenztyp Bildupload" (7 Lizenztypen, Personen-/Sachrechte-Status, bedingte Pflichtfelder, Risikohinweise) - Veroeffentlichungs-Box vereinfacht (Embargo aus der Form-UI entfernt), geplante Termine in Europe/Berlin (Speicherung UTC, DISPLAY_TIMEZONE) - Quota-Stub (users.press_release_quota) + monatlicher Reset-Command - Einreichungs-Modal einheitlich in Show/Create/Edit; Ghost-Buttons auf filled; PM-Editor-Layout responsive entkoppelt (.pr-editor-layout) KI-Pruef-Pipeline (Phasen 1-5 des Entwicklungsplans): - API-Haertung: status nicht mehr per API setzbar, eigene Submit-Route durch denselben Funnel (Blacklist, Quota, Status-Log) - Klassifikation Rot/Gelb/Gruen asynchron (Queue classification, OpenAI-Treiber + deterministischer Fallback), ki_audits-Audit-Log - Routing: Rot -> rejected + Mail, Gelb -> Review-Queue, Gruen -> Auto-Publish; Scheduler publiziert nur gruene faellige PMs - Content-Score 0-100 -> Stufe (Standard/Geprueft/Hochwertig) inkl. Editor-Panel und Badges; Re-Klassifikation/-Score bei Aenderung - Admin: KI-Badge + Filter, On-Demand-Pruefung mit Anbieter-Override Suite: 442 passed, 4 skipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0efabaf446
commit
a000238ca8
141 changed files with 5922 additions and 1001 deletions
|
|
@ -123,7 +123,7 @@ new #[Layout('components.layouts.app'), Title('Kategorie anlegen')] class extend
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" :href="route('admin.categories.index')" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" :href="route('admin.categories.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ new #[Layout('components.layouts.app'), Title('Kategorie bearbeiten')] class ext
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" :href="route('admin.categories.index')" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" :href="route('admin.categories.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ new #[Layout('components.layouts.app'), Title('Kategorien')] class extends Compo
|
|||
@endif
|
||||
<flux:button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="pencil"
|
||||
:href="route('admin.categories.edit', $category->id)"
|
||||
wire:navigate
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -271,7 +271,14 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firmenlogo') }}</flux:label>
|
||||
<flux:input type="file" wire:model="logo" accept="image/*" />
|
||||
<flux:file-upload wire:model="logo" accept="image/*">
|
||||
<flux:file-upload.dropzone
|
||||
:heading="__('Logo hierher ziehen oder klicken')"
|
||||
:text="__('Bilddatei · empfohlen quadratisch, min. 400×400 px')"
|
||||
with-progress
|
||||
inline
|
||||
/>
|
||||
</flux:file-upload>
|
||||
<flux:description>{{ __('Maximal 1 MB. Empfohlen: quadratisch, min. 400x400px') }}</flux:description>
|
||||
<flux:error name="logo" />
|
||||
|
||||
|
|
@ -298,7 +305,7 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
<span class="section-eyebrow">{{ __('Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary">
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.companies.show', $companyId) }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.companies.show', $companyId) }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -350,7 +350,14 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firmenlogo') }}</flux:label>
|
||||
<flux:input type="file" wire:model="logo" accept="image/jpeg,image/png,image/webp,image/gif" />
|
||||
<flux:file-upload wire:model="logo" accept="image/jpeg,image/png,image/webp,image/gif">
|
||||
<flux:file-upload.dropzone
|
||||
:heading="__('Logo hierher ziehen oder klicken')"
|
||||
:text="__('JPG, PNG, WebP oder GIF · max. 4 MB')"
|
||||
with-progress
|
||||
inline
|
||||
/>
|
||||
</flux:file-upload>
|
||||
<flux:description>{{ __('Maximal 4 MB. Varianten (sq/wide) werden automatisch generiert.') }}</flux:description>
|
||||
<flux:error name="logo" />
|
||||
|
||||
|
|
@ -371,7 +378,7 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
<img src="{{ $current_logo_url }}" width="128" height="128"
|
||||
class="h-32 max-h-32 w-32 max-w-32 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)]">
|
||||
</div>
|
||||
<flux:button type="button" size="sm" variant="ghost" wire:click="$set('remove_logo', true)">
|
||||
<flux:button type="button" size="sm" variant="filled" wire:click="$set('remove_logo', true)">
|
||||
{{ __('Logo entfernen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -382,7 +389,7 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
<div class="flex-1">
|
||||
{{ __('Logo wird beim Speichern entfernt.') }}
|
||||
</div>
|
||||
<flux:button type="button" size="sm" variant="ghost" wire:click="$set('remove_logo', false)">
|
||||
<flux:button type="button" size="sm" variant="filled" wire:click="$set('remove_logo', false)">
|
||||
{{ __('Rückgängig') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -413,7 +420,7 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
</flux:button>
|
||||
</flux:modal.trigger>
|
||||
<div class="flex gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary">
|
||||
|
|
|
|||
|
|
@ -396,7 +396,7 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearUserSearch"
|
||||
title="{{ __('Usersuche zurücksetzen') }}"
|
||||
|
|
@ -437,7 +437,7 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearContactSearch"
|
||||
title="{{ __('Kontaktsuche zurücksetzen') }}"
|
||||
|
|
@ -481,9 +481,9 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
|
||||
<flux:table.cell>
|
||||
<div class="flex gap-2">
|
||||
<flux:button size="sm" variant="ghost" icon="pencil"
|
||||
<flux:button size="sm" variant="filled" icon="pencil"
|
||||
href="{{ route('admin.companies.edit', $company->id) }}" wire:navigate />
|
||||
<flux:button size="sm" variant="ghost" icon="eye"
|
||||
<flux:button size="sm" variant="filled" icon="eye"
|
||||
href="{{ route('admin.companies.show', $company->id) }}" wire:navigate />
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
|
|
@ -532,7 +532,7 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
@if ($company->press_releases_count > 0)
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
href="{{ route('admin.press-releases.index', ['company' => $company->id]) }}"
|
||||
wire:navigate
|
||||
>
|
||||
|
|
@ -547,7 +547,7 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
@if ($company->contacts_count > 0)
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
href="{{ route('admin.contacts.index', ['company' => $company->id]) }}"
|
||||
wire:navigate
|
||||
>
|
||||
|
|
|
|||
|
|
@ -253,11 +253,11 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.companies.contacts.create'))
|
||||
<flux:button variant="ghost" icon="user-plus" href="{{ route('admin.companies.contacts.create', ['companyId' => $company->id]) }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="user-plus" href="{{ route('admin.companies.contacts.create', ['companyId' => $company->id]) }}" wire:navigate>
|
||||
{{ __('Kontakt hinzufügen') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
|
|
@ -344,7 +344,7 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
<article class="panel lg:col-span-2">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktuelle Pressemitteilungen') }}</span>
|
||||
<flux:button size="sm" variant="ghost" href="{{ route('admin.press-releases.index', ['company' => $company->id]) }}" wire:navigate>
|
||||
<flux:button size="sm" variant="filled" href="{{ route('admin.press-releases.index', ['company' => $company->id]) }}" wire:navigate>
|
||||
{{ __('Alle anzeigen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -454,7 +454,7 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
@endif
|
||||
</div>
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.edit'))
|
||||
<flux:button size="sm" variant="ghost" icon="pencil" href="{{ route('admin.contacts.edit', $contact->id) }}" wire:navigate />
|
||||
<flux:button size="sm" variant="filled" icon="pencil" href="{{ route('admin.contacts.edit', $contact->id) }}" wire:navigate />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -283,7 +283,7 @@ new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends
|
|||
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary">
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ new #[Layout('components.layouts.app'), Title('Kontakt bearbeiten')] class exten
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -330,7 +330,7 @@ new #[Layout('components.layouts.app'), Title('Kontakt bearbeiten')] class exten
|
|||
</flux:button>
|
||||
</flux:modal.trigger>
|
||||
<div class="flex gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary">
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearCompanySearch"
|
||||
title="{{ __('Firmensuche zurücksetzen') }}"
|
||||
|
|
@ -526,7 +526,7 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearUserSearch"
|
||||
title="{{ __('Usersuche zurücksetzen') }}"
|
||||
|
|
@ -559,7 +559,7 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
<div class="flex flex-1 gap-3">
|
||||
<flux:input wire:model="presetName" placeholder="{{ __('Neues Preset speichern...') }}"
|
||||
class="flex-1" />
|
||||
<flux:button wire:click="savePreset" variant="ghost" icon="bookmark">
|
||||
<flux:button wire:click="savePreset" variant="filled" icon="bookmark">
|
||||
{{ __('Preset speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -573,8 +573,8 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
<flux:button wire:click="applyPreset" variant="ghost">{{ __('Anwenden') }}</flux:button>
|
||||
<flux:button wire:click="setDefaultPreset" variant="ghost">{{ __('Als Standard') }}</flux:button>
|
||||
<flux:button wire:click="applyPreset" variant="filled">{{ __('Anwenden') }}</flux:button>
|
||||
<flux:button wire:click="setDefaultPreset" variant="filled">{{ __('Als Standard') }}</flux:button>
|
||||
<flux:button wire:click="deletePreset" variant="danger">{{ __('Löschen') }}</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -622,11 +622,11 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
<flux:table.cell>
|
||||
<div class="flex gap-2">
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.edit'))
|
||||
<flux:button size="sm" variant="ghost" icon="pencil"
|
||||
<flux:button size="sm" variant="filled" icon="pencil"
|
||||
href="{{ route('admin.contacts.edit', $contact->id) }}" wire:navigate />
|
||||
@endif
|
||||
@if ($contact->company && \Illuminate\Support\Facades\Route::has('admin.companies.show'))
|
||||
<flux:button size="sm" variant="ghost" icon="building-office"
|
||||
<flux:button size="sm" variant="filled" icon="building-office"
|
||||
href="{{ route('admin.companies.show', $contact->company_id) }}"
|
||||
wire:navigate />
|
||||
@endif
|
||||
|
|
@ -674,7 +674,7 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
@if ($contact->press_releases_count > 0)
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
href="{{ route('admin.press-releases.index', ['contact' => $contact->id]) }}"
|
||||
wire:navigate
|
||||
>
|
||||
|
|
@ -694,11 +694,11 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
<flux:table.cell>
|
||||
<div class="flex gap-2">
|
||||
<flux:modal.trigger name="confirm-contact-delete-{{ $contact->id }}">
|
||||
<flux:button size="sm" variant="ghost" icon="trash" type="button"
|
||||
<flux:button size="sm" variant="filled" icon="trash" type="button"
|
||||
x-data=""
|
||||
x-on:click.prevent="$dispatch('open-modal', 'confirm-contact-delete-{{ $contact->id }}')" />
|
||||
</flux:modal.trigger>
|
||||
<flux:button size="sm" variant="ghost" icon="envelope"
|
||||
<flux:button size="sm" variant="filled" icon="envelope"
|
||||
href="mailto:{{ $contact->email }}" />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ new #[Layout('components.layouts.app'), Title('Footer-Code anlegen')] class exte
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -194,7 +194,7 @@ new #[Layout('components.layouts.app'), Title('Footer-Code anlegen')] class exte
|
|||
|
||||
<article class="panel">
|
||||
<div class="p-5 flex items-center justify-end gap-2">
|
||||
<flux:button variant="ghost" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
<flux:button variant="filled" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary" icon="check">
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ new #[Layout('components.layouts.app'), Title('Footer-Code bearbeiten')] class e
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -233,7 +233,7 @@ new #[Layout('components.layouts.app'), Title('Footer-Code bearbeiten')] class e
|
|||
</flux:button>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:button variant="ghost" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
<flux:button variant="filled" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary" icon="check">
|
||||
|
|
|
|||
|
|
@ -215,14 +215,14 @@ new #[Layout('components.layouts.app'), Title('Footer-Codes')] class extends Com
|
|||
<div class="flex items-center justify-end gap-1">
|
||||
<flux:button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
:icon="$code->is_active ? 'pause' : 'play'"
|
||||
wire:click="toggleActive({{ $code->id }})"
|
||||
:title="$code->is_active ? __('Deaktivieren') : __('Aktivieren')"
|
||||
/>
|
||||
<flux:button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="pencil"
|
||||
:href="route('admin.footer-codes.edit', $code->id)"
|
||||
wire:navigate
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
|
||||
<flux:button size="sm" variant="ghost" icon="arrow-path" wire:click="resetFilters">
|
||||
<flux:button size="sm" variant="filled" icon="arrow-path" wire:click="resetFilters">
|
||||
{{ __('Filter zurücksetzen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -260,7 +260,7 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
<div class="space-y-0.5">
|
||||
<flux:button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
:href="route('admin.users.show', $invoice->user)"
|
||||
wire:navigate
|
||||
>
|
||||
|
|
@ -297,7 +297,7 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
<div class="flex items-center gap-2">
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="arrow-top-right-on-square"
|
||||
:href="route('admin.legacy-invoices.pdf', $invoice)"
|
||||
target="_blank"
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ new #[Layout('components.layouts.app'), Title('Newsletter Sync')] class extends
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button size="sm" variant="ghost" icon="eye" wire:click="triggerDryRun">
|
||||
<flux:button size="sm" variant="filled" icon="eye" wire:click="triggerDryRun">
|
||||
{{ __('Dry Run') }}
|
||||
</flux:button>
|
||||
<flux:button size="sm" variant="primary" icon="play" wire:click="triggerTestSync">
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ new #[Layout('components.layouts.app'), Title('Neue Voreinstellung')] class exte
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -77,7 +77,7 @@ new #[Layout('components.layouts.app'), Title('Neue Voreinstellung')] class exte
|
|||
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary">
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ new #[Layout('components.layouts.app'), Title('Voreinstellung bearbeiten')] clas
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -99,7 +99,7 @@ new #[Layout('components.layouts.app'), Title('Voreinstellung bearbeiten')] clas
|
|||
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary">
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ new #[Layout('components.layouts.app'), Title('Voreinstellungen')] class extends
|
|||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
<flux:button size="sm" variant="ghost" icon="pencil" href="{{ route('admin.presets.edit', $preset->id) }}" wire:navigate />
|
||||
<flux:button size="sm" variant="filled" icon="pencil" href="{{ route('admin.presets.edit', $preset->id) }}" wire:navigate />
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ use Livewire\Attributes\Layout;
|
|||
use Livewire\Attributes\Title;
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class extends Component
|
||||
{
|
||||
new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class extends Component {
|
||||
public string $portal = 'presseecho';
|
||||
|
||||
public string $language = 'de';
|
||||
|
|
@ -52,6 +51,10 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
|
||||
public ?string $scheduledAt = null;
|
||||
|
||||
public ?string $scheduledDate = null;
|
||||
|
||||
public ?string $scheduledTime = null;
|
||||
|
||||
public bool $useEmbargo = false;
|
||||
|
||||
public ?string $embargoAt = null;
|
||||
|
|
@ -61,9 +64,33 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
$this->resetErrorBag('companyId');
|
||||
}
|
||||
|
||||
public function updatedPublishMode(): void
|
||||
{
|
||||
$this->syncScheduledAt();
|
||||
|
||||
if ($this->publishMode === 'now') {
|
||||
$this->scheduledDate = null;
|
||||
$this->scheduledTime = null;
|
||||
$this->scheduledAt = null;
|
||||
$this->resetErrorBag(['scheduledDate', 'scheduledTime', 'scheduledAt']);
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedScheduledDate(): void
|
||||
{
|
||||
$this->syncScheduledAt();
|
||||
$this->validateScheduledAtWhenReady();
|
||||
}
|
||||
|
||||
public function updatedScheduledTime(): void
|
||||
{
|
||||
$this->syncScheduledAt();
|
||||
$this->validateScheduledAtWhenReady();
|
||||
}
|
||||
|
||||
public function updatedCompanyId(): void
|
||||
{
|
||||
if (! $this->companyId) {
|
||||
if (!$this->companyId) {
|
||||
$this->contactId = null;
|
||||
|
||||
return;
|
||||
|
|
@ -71,7 +98,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
|
||||
$contactStillValid = $this->companyContact((int) $this->contactId, (int) $this->companyId);
|
||||
|
||||
if (! $contactStillValid) {
|
||||
if (!$contactStillValid) {
|
||||
$this->contactId = $this->defaultContactIdFor((int) $this->companyId);
|
||||
}
|
||||
|
||||
|
|
@ -109,10 +136,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
|
||||
public function removeTag(string $tag): void
|
||||
{
|
||||
$existing = array_values(array_filter(
|
||||
$this->tagsArray(),
|
||||
fn (string $existingTag): bool => $existingTag !== $tag,
|
||||
));
|
||||
$existing = array_values(array_filter($this->tagsArray(), fn(string $existingTag): bool => $existingTag !== $tag));
|
||||
|
||||
$this->keywords = implode(', ', $existing);
|
||||
|
||||
|
|
@ -128,7 +152,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
protected function formRules(): array
|
||||
{
|
||||
$rules = [
|
||||
'portal' => ['required', Rule::in(array_map(fn (Portal $p) => $p->value, Portal::cases()))],
|
||||
'portal' => ['required', Rule::in(array_map(fn(Portal $p) => $p->value, Portal::cases()))],
|
||||
'language' => ['required', Rule::in(['de', 'en'])],
|
||||
'companyId' => ['required', 'integer', Rule::exists('companies', 'id')],
|
||||
'categoryId' => ['required', 'integer', Rule::exists('categories', 'id')],
|
||||
|
|
@ -143,26 +167,103 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
];
|
||||
|
||||
if ($this->publishMode === 'scheduled') {
|
||||
$rules['scheduledAt'] = ['required', 'date', 'after:'.now()->addMinutes(5)->toIso8601String()];
|
||||
$rules['scheduledDate'] = ['required', 'date'];
|
||||
$rules['scheduledTime'] = ['required', 'date_format:H:i'];
|
||||
$rules['scheduledAt'] = [
|
||||
'required',
|
||||
'date',
|
||||
// Termin wird in Europe/Berlin erfasst; deshalb hier zeitzonen-
|
||||
// bewusst prüfen statt über die naive `after:`-Regel.
|
||||
function (string $attribute, mixed $value, \Closure $fail): void {
|
||||
$scheduledAt = $this->scheduledAtUtc();
|
||||
|
||||
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
|
||||
$fail(__('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
|
||||
}
|
||||
},
|
||||
];
|
||||
} else {
|
||||
$rules['scheduledDate'] = ['nullable'];
|
||||
$rules['scheduledTime'] = ['nullable'];
|
||||
$rules['scheduledAt'] = ['nullable'];
|
||||
}
|
||||
|
||||
if ($this->useEmbargo) {
|
||||
$rules['embargoAt'] = ['required', 'date', 'after:'.now()->toIso8601String()];
|
||||
} else {
|
||||
$rules['embargoAt'] = ['nullable'];
|
||||
}
|
||||
$rules['embargoAt'] = ['nullable'];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
protected function syncScheduledAt(): void
|
||||
{
|
||||
if ($this->publishMode !== 'scheduled') {
|
||||
$this->scheduledAt = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (blank($this->scheduledDate) && blank($this->scheduledTime) && filled($this->scheduledAt)) {
|
||||
$scheduledAt = \Carbon\Carbon::parse($this->scheduledAt);
|
||||
$this->scheduledDate = $scheduledAt->format('Y-m-d');
|
||||
$this->scheduledTime = $scheduledAt->format('H:i');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (blank($this->scheduledDate) || blank($this->scheduledTime)) {
|
||||
$this->scheduledAt = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->scheduledAt = "{$this->scheduledDate}T{$this->scheduledTime}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Wandelt den in Europe/Berlin erfassten Termin in den UTC-Zeitpunkt,
|
||||
* wie er in der Datenbank gespeichert wird. Null, wenn kein Termin gesetzt.
|
||||
*/
|
||||
protected function scheduledAtUtc(): ?\Carbon\Carbon
|
||||
{
|
||||
if (blank($this->scheduledAt)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \Carbon\Carbon::parse($this->scheduledAt, PressRelease::DISPLAY_TIMEZONE)->utc();
|
||||
}
|
||||
|
||||
protected function validateScheduledAtWhenReady(): void
|
||||
{
|
||||
if (blank($this->scheduledAt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resetErrorBag('scheduledAt');
|
||||
|
||||
$scheduledAt = $this->scheduledAtUtc();
|
||||
|
||||
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
|
||||
$this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->validateOnly('scheduledAt', $this->formRules());
|
||||
} catch (\Illuminate\Validation\ValidationException) {
|
||||
// Termin bleibt invalid; Bag wird automatisch befüllt.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Live-Re-Validation für bereits invalide Felder.
|
||||
*
|
||||
* Die Termin-Synchronisierung liegt vollständig in den spezifischen
|
||||
* `updated{PublishMode,ScheduledDate,ScheduledTime}`-Hooks; hier bleibt
|
||||
* nur die generische Re-Validierung bereits fehlerhafter Felder.
|
||||
*/
|
||||
public function updated(string $property): void
|
||||
{
|
||||
if (! $this->getErrorBag()->has($property)) {
|
||||
if (!$this->getErrorBag()->has($property)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -175,22 +276,17 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
|
||||
protected function notifyValidationError(?\Illuminate\Validation\ValidationException $exception = null): void
|
||||
{
|
||||
$count = $exception
|
||||
? array_sum(array_map('count', $exception->errors()))
|
||||
: count($this->getErrorBag()->all());
|
||||
$count = $exception ? array_sum(array_map('count', $exception->errors())) : count($this->getErrorBag()->all());
|
||||
|
||||
Flux::toast(
|
||||
heading: __('Bitte Eingaben prüfen'),
|
||||
text: $count > 1
|
||||
? __(':count Felder benötigen deine Aufmerksamkeit.', ['count' => $count])
|
||||
: __('Ein Feld benötigt deine Aufmerksamkeit.'),
|
||||
variant: 'danger',
|
||||
duration: 6000,
|
||||
);
|
||||
Flux::toast(heading: __('Bitte Eingaben prüfen'), text: $count > 1 ? __(':count Felder benötigen deine Aufmerksamkeit.', ['count' => $count]) : __('Ein Feld benötigt deine Aufmerksamkeit.'), variant: 'danger', duration: 6000);
|
||||
}
|
||||
|
||||
public function save(string $submitStatus = 'draft'): void
|
||||
{
|
||||
$this->syncScheduledAt();
|
||||
$this->useEmbargo = false;
|
||||
$this->embargoAt = null;
|
||||
|
||||
try {
|
||||
$this->validate($this->formRules());
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
|
|
@ -204,7 +300,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
default => PressReleaseStatus::Draft,
|
||||
};
|
||||
|
||||
$slug = (new PressRelease)->generateUniqueSlug($this->title, [
|
||||
$slug = new PressRelease()->generateUniqueSlug($this->title, [
|
||||
'portal' => $this->portal,
|
||||
'language' => $this->language,
|
||||
]);
|
||||
|
|
@ -222,17 +318,11 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
'subtitle' => trim($this->subtitle) ?: null,
|
||||
'slug' => $slug,
|
||||
'text' => $cleanText,
|
||||
'boilerplate_override' => $this->useBoilerplateOverride && trim($this->boilerplateOverride) !== ''
|
||||
? trim($this->boilerplateOverride)
|
||||
: null,
|
||||
'boilerplate_override' => $this->useBoilerplateOverride && trim($this->boilerplateOverride) !== '' ? trim($this->boilerplateOverride) : null,
|
||||
'keywords' => $this->keywords ?: null,
|
||||
'backlink_url' => $this->backlinkUrl ?: null,
|
||||
'scheduled_at' => $this->publishMode === 'scheduled' && $this->scheduledAt
|
||||
? \Carbon\Carbon::parse($this->scheduledAt)
|
||||
: null,
|
||||
'embargo_at' => $this->useEmbargo && $this->embargoAt
|
||||
? \Carbon\Carbon::parse($this->embargoAt)
|
||||
: null,
|
||||
'scheduled_at' => $this->publishMode === 'scheduled' ? $this->scheduledAtUtc() : null,
|
||||
'embargo_at' => null,
|
||||
'status' => $status->value,
|
||||
'no_export' => $this->noExport,
|
||||
]);
|
||||
|
|
@ -244,20 +334,14 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
}
|
||||
}
|
||||
|
||||
Flux::toast(
|
||||
heading: $status === PressReleaseStatus::Review ? __('Eingereicht') : __('Entwurf gespeichert'),
|
||||
text: $status === PressReleaseStatus::Review
|
||||
? __('Pressemitteilung zur Prüfung eingereicht.')
|
||||
: __('Pressemitteilung als Entwurf gespeichert.'),
|
||||
variant: 'success',
|
||||
);
|
||||
Flux::toast(heading: $status === PressReleaseStatus::Review ? __('Eingereicht') : __('Entwurf gespeichert'), text: $status === PressReleaseStatus::Review ? __('Pressemitteilung zur Prüfung eingereicht.') : __('Pressemitteilung als Entwurf gespeichert.'), variant: 'success');
|
||||
|
||||
$this->redirect(route('admin.press-releases.edit', $pr->id), navigate: true);
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
$term = trim($this->companySearch);
|
||||
$term = Portal::stripTrailingAbbreviation($this->companySearch);
|
||||
|
||||
$companies = Company::withoutGlobalScopes()
|
||||
->when(filled($term), function ($q) use ($term): void {
|
||||
|
|
@ -267,27 +351,22 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
return;
|
||||
}
|
||||
|
||||
$q->where('name', 'like', '%'.$term.'%')
|
||||
->orWhere('slug', 'like', '%'.$term.'%');
|
||||
$q->where('name', 'like', '%' . $term . '%')->orWhere('slug', 'like', '%' . $term . '%');
|
||||
})
|
||||
->when(blank($term) && $this->companyId, fn ($q) => $q->whereIn('id', [(int) $this->companyId]))
|
||||
->when(blank($term) && ! $this->companyId, fn ($q) => $q->whereRaw('0 = 1'))
|
||||
->when(blank($term) && $this->companyId, fn($q) => $q->whereIn('id', [(int) $this->companyId]))
|
||||
->when(blank($term) && !$this->companyId, fn($q) => $q->whereRaw('0 = 1'))
|
||||
->orderBy('name')
|
||||
->limit(50)
|
||||
->get(['id', 'name']);
|
||||
|
||||
$selectedCompany = $this->companyId
|
||||
? Company::withoutGlobalScopes()->find((int) $this->companyId)
|
||||
: null;
|
||||
$selectedCompany = $this->companyId ? Company::withoutGlobalScopes()->find((int) $this->companyId) : null;
|
||||
|
||||
return [
|
||||
'companies' => $companies,
|
||||
'categories' => $this->categoryOptions(),
|
||||
'portalOptions' => array_filter(Portal::cases(), fn (Portal $p) => $p !== Portal::Both),
|
||||
'portalOptions' => array_filter(Portal::cases(), fn(Portal $p) => $p !== Portal::Both),
|
||||
'selectedCompany' => $selectedCompany,
|
||||
'selectedCompanyContacts' => $selectedCompany
|
||||
? $this->companyContacts((int) $selectedCompany->id)
|
||||
: Contact::query()->whereRaw('0 = 1')->get(),
|
||||
'selectedCompanyContacts' => $selectedCompany ? $this->companyContacts((int) $selectedCompany->id) : Contact::query()->whereRaw('0 = 1')->get(),
|
||||
'tagSuggestions' => $this->tagSuggestionsFor($selectedCompany),
|
||||
];
|
||||
}
|
||||
|
|
@ -340,9 +419,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
'key' => 'tags',
|
||||
'status' => $tagsCount >= 1 ? 'ok' : 'warn',
|
||||
'label' => __('Themen-Tags vergeben'),
|
||||
'sub' => $tagsCount >= 1
|
||||
? trans_choice('{1}:n Tag|[2,*]:n Tags', $tagsCount, ['n' => $tagsCount])
|
||||
: __('empfohlen für SEO & Auffindbarkeit'),
|
||||
'sub' => $tagsCount >= 1 ? trans_choice('{1}:n Tag|[2,*]:n Tags', $tagsCount, ['n' => $tagsCount]) : __('empfohlen für SEO & Auffindbarkeit'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
@ -357,7 +434,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
}
|
||||
|
||||
return collect(explode(',', $this->keywords))
|
||||
->map(fn (string $tag): string => trim($tag))
|
||||
->map(fn(string $tag): string => trim($tag))
|
||||
->filter()
|
||||
->unique()
|
||||
->values()
|
||||
|
|
@ -391,10 +468,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
return null;
|
||||
}
|
||||
|
||||
return Contact::withoutGlobalScopes()
|
||||
->where('company_id', $companyId)
|
||||
->whereKey($contactId)
|
||||
->first();
|
||||
return Contact::withoutGlobalScopes()->where('company_id', $companyId)->whereKey($contactId)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -402,43 +476,27 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
*/
|
||||
private function tagSuggestionsFor(?Company $company): array
|
||||
{
|
||||
$defaults = [
|
||||
__('Mittelstand'),
|
||||
__('Unternehmen'),
|
||||
__('Eröffnung'),
|
||||
__('Innovation'),
|
||||
__('Nachhaltigkeit'),
|
||||
];
|
||||
$defaults = [__('Mittelstand'), __('Unternehmen'), __('Eröffnung'), __('Innovation'), __('Nachhaltigkeit')];
|
||||
|
||||
if (! $company) {
|
||||
if (!$company) {
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter([
|
||||
$company->portal?->label(),
|
||||
$company->country_code === 'DE' ? __('Deutschland') : null,
|
||||
...$defaults,
|
||||
])));
|
||||
return array_values(array_unique(array_filter([$company->portal?->label(), $company->country_code === 'DE' ? __('Deutschland') : null, ...$defaults])));
|
||||
}
|
||||
|
||||
private function categoryOptions(): Collection
|
||||
{
|
||||
return app(AdminPerformanceCache::class)->remember(AdminPerformanceCache::ActiveCategoryOptions, AdminPerformanceCache::OptionsTtl, fn () => Category::query()
|
||||
->with('translations')
|
||||
->where('is_active', true)
|
||||
->orderBy('id')
|
||||
->get());
|
||||
return app(AdminPerformanceCache::class)->remember(AdminPerformanceCache::ActiveCategoryOptions, AdminPerformanceCache::OptionsTtl, fn() => Category::query()->with('translations')->where('is_active', true)->orderBy('id')->get());
|
||||
}
|
||||
|
||||
private function supportsFullTextSearch(string $term): bool
|
||||
{
|
||||
return mb_strlen($term) >= 3
|
||||
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
return mb_strlen($term) >= 3 && in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
|
||||
}
|
||||
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-8" x-data="{ tagInput: '' }">
|
||||
<div class="space-y-8 pr-editor-shell" x-data="{ tagInput: '' }">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
|
|
@ -455,39 +513,37 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.press-releases.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.press-releases.index') }}"
|
||||
wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ============== 2-COLUMN GRID ============== --}}
|
||||
<div class="grid gap-6 lg:grid-cols-[minmax(0,1fr)_360px]">
|
||||
<div class="grid gap-6 pr-editor-layout">
|
||||
|
||||
{{-- =================== LINKS: SCHREIBFLÄCHE =================== --}}
|
||||
<div class="space-y-6 min-w-0">
|
||||
|
||||
{{-- 1) FIRMA-SELEKTOR --}}
|
||||
<section class="panel">
|
||||
<div class="p-4 flex flex-wrap items-center gap-4">
|
||||
<span class="pr-form-label" style="margin-bottom:0;">{{ __('Für Firma') }} <span class="req">*</span></span>
|
||||
<div class="min-w-[260px]">
|
||||
<flux:select
|
||||
wire:model.live="companyId"
|
||||
variant="combobox"
|
||||
:filter="false"
|
||||
clearable
|
||||
placeholder="{{ __('Firma suchen…') }}"
|
||||
>
|
||||
<div class="p-4 space-y-3">
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4">
|
||||
<span class="pr-form-label shrink-0" style="margin-bottom:0;">
|
||||
{{ __('Für Firma') }} <span class="req">*</span>
|
||||
</span>
|
||||
<flux:select wire:model.live="companyId" variant="combobox" :filter="false" clearable
|
||||
placeholder="{{ __('Firma suchen…') }}" class="w-full sm:flex-1">
|
||||
<x-slot name="input">
|
||||
<flux:select.input
|
||||
wire:model.live.debounce.300ms="companySearch"
|
||||
placeholder="{{ __('Name eingeben…') }}"
|
||||
/>
|
||||
<flux:select.input wire:model.live.debounce.300ms="companySearch"
|
||||
placeholder="{{ __('Name eingeben…') }}" />
|
||||
</x-slot>
|
||||
@foreach ($companies as $company)
|
||||
<flux:select.option :value="$company->id" wire:key="{{ $company->id }}">
|
||||
{{ $company->name }}
|
||||
{{ $company->name }} @if ($company->portal)
|
||||
({{ $company->portal->abbreviation() }})
|
||||
@endif
|
||||
</flux:select.option>
|
||||
@endforeach
|
||||
<x-slot name="empty">
|
||||
|
|
@ -501,16 +557,17 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</x-slot>
|
||||
</flux:select>
|
||||
</div>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Boilerplate und Pressekontakt werden bei Wechsel angepasst.') }}
|
||||
</span>
|
||||
<span class="flex-1"></span>
|
||||
@if ($selectedCompany)
|
||||
<flux:button size="sm" variant="ghost" icon="building-office"
|
||||
href="{{ route('admin.companies.show', $selectedCompany->id) }}" wire:navigate>
|
||||
{{ __('Firmenprofil') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Boilerplate und Pressekontakt werden bei Wechsel angepasst.') }}
|
||||
</span>
|
||||
@if ($selectedCompany)
|
||||
<flux:button size="sm" variant="filled" icon="building-office"
|
||||
href="{{ route('admin.companies.show', $selectedCompany->id) }}" wire:navigate>
|
||||
{{ __('Firmenprofil') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<flux:error name="companyId" />
|
||||
</section>
|
||||
|
|
@ -525,7 +582,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<div class="flex items-center gap-3">
|
||||
@php
|
||||
$titleLen = mb_strlen($title);
|
||||
$titleClass = $titleLen >= 40 && $titleLen <= 90 ? 'good' : ($titleLen > 0 ? 'warn' : '');
|
||||
$titleClass =
|
||||
$titleLen >= 40 && $titleLen <= 90 ? 'good' : ($titleLen > 0 ? 'warn' : '');
|
||||
$titleBar = min(100, max(0, ($titleLen / 100) * 100));
|
||||
@endphp
|
||||
<span class="pr-meter {{ $titleClass }}">
|
||||
|
|
@ -535,11 +593,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<span class="pr-bald-badge">{{ __('KI-Titel · bald') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="title"
|
||||
placeholder="{{ __('Aussagekräftiger Titel — wer, was, wo?') }}"
|
||||
size="lg"
|
||||
/>
|
||||
<flux:input wire:model.live.debounce.300ms="title"
|
||||
placeholder="{{ __('Aussagekräftiger Titel — wer, was, wo?') }}" size="lg" />
|
||||
<p class="pr-form-help">
|
||||
{{ __('40–90 Zeichen empfohlen. Konkret, ohne Marketing-Floskeln.') }}
|
||||
</p>
|
||||
|
|
@ -553,7 +608,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<div class="flex items-center justify-between mb-2 gap-4">
|
||||
<span class="pr-form-label" style="margin-bottom:0;">
|
||||
{{ __('Untertitel') }}
|
||||
<span class="text-[color:var(--color-ink-4)] font-normal" style="letter-spacing:0;text-transform:none;">
|
||||
<span class="text-[color:var(--color-ink-4)] font-normal"
|
||||
style="letter-spacing:0;text-transform:none;">
|
||||
— {{ __('optional') }}
|
||||
</span>
|
||||
</span>
|
||||
|
|
@ -566,10 +622,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
{{ $subLen }} / 200
|
||||
</span>
|
||||
</div>
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="subtitle"
|
||||
placeholder="{{ __('Kurzer Zusatztext / Dachzeile…') }}"
|
||||
/>
|
||||
<flux:input wire:model.live.debounce.300ms="subtitle"
|
||||
placeholder="{{ __('Kurzer Zusatztext / Dachzeile…') }}" />
|
||||
<flux:error name="subtitle" />
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -583,7 +637,9 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</span>
|
||||
<div class="flex items-center gap-3">
|
||||
@php
|
||||
$textLen = app(\App\Services\PressRelease\PressReleaseHtmlSanitizer::class)->plainTextLength($text);
|
||||
$textLen = app(
|
||||
\App\Services\PressRelease\PressReleaseHtmlSanitizer::class,
|
||||
)->plainTextLength($text);
|
||||
$textClass = $textLen >= 600 ? 'good' : ($textLen >= 50 ? 'warn' : '');
|
||||
$textBar = min(100, max(0, ($textLen / 3500) * 100));
|
||||
@endphp
|
||||
|
|
@ -594,11 +650,9 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<span class="pr-bald-badge">{{ __('KI-Lektorat · bald') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<flux:editor
|
||||
wire:model.live.debounce.500ms="text"
|
||||
<flux:editor wire:model.live.debounce.500ms="text"
|
||||
toolbar="heading | bold italic | bullet ordered blockquote | link | undo redo"
|
||||
placeholder="{{ __('Hier weiterschreiben…') }}"
|
||||
/>
|
||||
placeholder="{{ __('Hier weiterschreiben…') }}" />
|
||||
<flux:error name="text" />
|
||||
|
||||
<div class="pr-ai-hint mt-4">
|
||||
|
|
@ -619,14 +673,13 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<div class="flex items-center justify-between mb-3 gap-4">
|
||||
<span class="pr-form-label" style="margin-bottom:0;">
|
||||
{{ __('Über das Unternehmen') }}
|
||||
<span class="text-[color:var(--color-ink-4)] font-normal" style="letter-spacing:0;text-transform:none;">
|
||||
<span class="text-[color:var(--color-ink-4)] font-normal"
|
||||
style="letter-spacing:0;text-transform:none;">
|
||||
— {{ __('Boilerplate aus Firma') }}
|
||||
</span>
|
||||
</span>
|
||||
<flux:checkbox
|
||||
wire:model.live="useBoilerplateOverride"
|
||||
:label="__('Für diese PM überschreiben')"
|
||||
/>
|
||||
<flux:checkbox wire:model.live="useBoilerplateOverride"
|
||||
:label="__('Für diese PM überschreiben')" />
|
||||
</div>
|
||||
|
||||
@if ($selectedCompany?->boilerplate)
|
||||
|
|
@ -634,7 +687,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<p class="m-0">{!! nl2br(e($selectedCompany->boilerplate)) !!}</p>
|
||||
@if ($selectedCompany->website)
|
||||
<p class="m-0 text-[12px] text-[color:var(--color-ink-3)] mt-3">
|
||||
<span class="font-semibold text-[color:var(--color-ink-2)]">{{ __('Web') }}:</span>
|
||||
<span
|
||||
class="font-semibold text-[color:var(--color-ink-2)]">{{ __('Web') }}:</span>
|
||||
{{ $selectedCompany->website }}
|
||||
</p>
|
||||
@endif
|
||||
|
|
@ -647,11 +701,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
|
||||
@if ($useBoilerplateOverride)
|
||||
<div class="mt-3">
|
||||
<flux:textarea
|
||||
wire:model.live.debounce.500ms="boilerplateOverride"
|
||||
rows="5"
|
||||
placeholder="{{ __('Boilerplate-Text speziell für diese PM…') }}"
|
||||
/>
|
||||
<flux:textarea wire:model.live.debounce.500ms="boilerplateOverride" rows="5"
|
||||
placeholder="{{ __('Boilerplate-Text speziell für diese PM…') }}" />
|
||||
<flux:error name="boilerplateOverride" />
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -666,7 +717,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
{{-- /Schreibfläche --}}
|
||||
|
||||
{{-- =================== RECHTS: SETTINGS-SIDEBAR =================== --}}
|
||||
<aside class="space-y-4 lg:sticky lg:top-4 self-start">
|
||||
<aside class="space-y-4 pr-editor-side self-start">
|
||||
|
||||
{{-- Aktionen + Pre-Submit-Check --}}
|
||||
<article class="panel" style="border-color:var(--color-hub);">
|
||||
|
|
@ -675,7 +726,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<span class="badge muted dot">{{ __('Neu') }}</span>
|
||||
</div>
|
||||
<div class="p-5">
|
||||
<div class="rounded-[4px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3 mb-3">
|
||||
<div
|
||||
class="rounded-[4px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3 mb-3">
|
||||
@php
|
||||
$okCount = collect($this->presubmitChecks)->where('status', 'ok')->count();
|
||||
$totalCount = count($this->presubmitChecks);
|
||||
|
|
@ -702,7 +754,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</span>
|
||||
<span class="lbl">
|
||||
{{ $check['label'] }}
|
||||
@if (! empty($check['sub']))
|
||||
@if (!empty($check['sub']))
|
||||
<span class="sub">{{ $check['sub'] }}</span>
|
||||
@endif
|
||||
</span>
|
||||
|
|
@ -710,14 +762,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
@endforeach
|
||||
</div>
|
||||
|
||||
<flux:button
|
||||
type="button"
|
||||
variant="primary"
|
||||
icon="paper-airplane"
|
||||
class="w-full"
|
||||
wire:click="save('review')"
|
||||
wire:loading.attr="disabled"
|
||||
>
|
||||
<flux:button type="button" variant="primary" icon="paper-airplane" class="w-full"
|
||||
wire:click="save('review')" wire:loading.attr="disabled">
|
||||
{{ __('Zur Prüfung einreichen') }}
|
||||
</flux:button>
|
||||
<p class="text-[11px] text-[color:var(--color-ink-3)] mt-2 leading-[1.45] m-0">
|
||||
|
|
@ -725,14 +771,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</p>
|
||||
|
||||
<hr class="border-0 border-t border-[color:var(--color-bg-rule)] my-3" />
|
||||
<flux:button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
icon="bookmark"
|
||||
class="w-full"
|
||||
wire:click="save('draft')"
|
||||
wire:loading.attr="disabled"
|
||||
>
|
||||
<flux:button type="button" variant="filled" icon="bookmark" class="w-full"
|
||||
wire:click="save('draft')" wire:loading.attr="disabled">
|
||||
{{ __('Als Entwurf speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -751,11 +791,13 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<flux:select wire:model.live="categoryId">
|
||||
<option value="">{{ __('Bitte wählen…') }}</option>
|
||||
@foreach ($categories as $cat)
|
||||
<option value="{{ $cat->id }}">{{ $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id }}</option>
|
||||
<option value="{{ $cat->id }}">
|
||||
{{ $cat->translations->firstWhere('locale', 'de')?->name ?? $cat->id }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
<flux:error name="categoryId" />
|
||||
<flux:description>{{ __('Pflichtfeld — steuert, in welcher Rubrik die PM erscheint.') }}</flux:description>
|
||||
<flux:description>{{ __('Pflichtfeld — steuert, in welcher Rubrik die PM erscheint.') }}
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
</div>
|
||||
</article>
|
||||
|
|
@ -785,14 +827,17 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</div>
|
||||
<div class="p-5">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Portal-Override') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:label>{{ __('Portal-Override') }} <span class="text-[color:var(--color-err)]">*</span>
|
||||
</flux:label>
|
||||
<flux:select wire:model.live="portal">
|
||||
@foreach ($portalOptions as $p)
|
||||
<option value="{{ $p->value }}">{{ $p->label() }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
<flux:error name="portal" />
|
||||
<flux:description>{{ __('Admin-Befugnis: Portal kann unabhängig von der Firma geändert werden.') }}</flux:description>
|
||||
<flux:description>
|
||||
{{ __('Admin-Befugnis: Portal kann unabhängig von der Firma geändert werden.') }}
|
||||
</flux:description>
|
||||
</flux:field>
|
||||
</div>
|
||||
</article>
|
||||
|
|
@ -812,7 +857,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
@endif
|
||||
</p>
|
||||
@if ($selectedCompany)
|
||||
<flux:button size="sm" variant="ghost" icon="plus" class="w-full"
|
||||
<flux:button size="sm" variant="filled" icon="plus" class="w-full"
|
||||
href="{{ route('admin.companies.show', $selectedCompany->id) }}" wire:navigate>
|
||||
{{ __('Kontakt im Firmenprofil anlegen') }}
|
||||
</flux:button>
|
||||
|
|
@ -824,8 +869,9 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<option value="">{{ __('Bitte wählen…') }}</option>
|
||||
@foreach ($selectedCompanyContacts as $contact)
|
||||
@php
|
||||
$contactName = trim(($contact->first_name ?? '').' '.($contact->last_name ?? ''))
|
||||
?: __('Kontakt #:n', ['n' => $contact->id]);
|
||||
$contactName =
|
||||
trim(($contact->first_name ?? '') . ' ' . ($contact->last_name ?? '')) ?:
|
||||
__('Kontakt #:n', ['n' => $contact->id]);
|
||||
$contactRole = $contact->responsibility ?: __('Kontakt');
|
||||
@endphp
|
||||
<option value="{{ $contact->id }}">
|
||||
|
|
@ -836,16 +882,18 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<flux:error name="contactId" />
|
||||
</flux:field>
|
||||
|
||||
@if (! $contactId)
|
||||
@if (!$contactId)
|
||||
<div class="flex items-start gap-2 text-[11.5px] text-[color:var(--color-warn)]">
|
||||
<flux:icon name="exclamation-triangle" variant="mini" class="size-4 flex-shrink-0 mt-0.5" />
|
||||
<flux:icon name="exclamation-triangle" variant="mini"
|
||||
class="size-4 flex-shrink-0 mt-0.5" />
|
||||
<span>{{ __('Noch kein Pressekontakt ausgewählt. Empfohlen, aber nicht zwingend.') }}</span>
|
||||
</div>
|
||||
@else
|
||||
@php($activeContact = $selectedCompanyContacts->firstWhere('id', (int) $contactId))
|
||||
@if ($activeContact && empty($activeContact->phone))
|
||||
<div class="flex items-start gap-2 text-[11.5px] text-[color:var(--color-warn)]">
|
||||
<flux:icon name="exclamation-triangle" variant="mini" class="size-4 flex-shrink-0 mt-0.5" />
|
||||
<flux:icon name="exclamation-triangle" variant="mini"
|
||||
class="size-4 flex-shrink-0 mt-0.5" />
|
||||
<span>{{ __('Kein Telefon hinterlegt — Journalisten greifen oft direkt zum Hörer.') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -859,15 +907,19 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Themen-Tags') }}</span>
|
||||
<span class="text-[10.5px] text-[color:var(--color-ink-4)]">
|
||||
<strong class="font-mono text-[color:var(--color-ink-2)]">{{ count($this->tags) }}</strong> / 5
|
||||
<strong class="font-mono text-[color:var(--color-ink-2)]">{{ count($this->tags) }}</strong> /
|
||||
5
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-3">
|
||||
<div class="border border-[color:var(--color-bg-rule)] rounded-[4px] bg-[color:var(--color-bg-card)] px-2 py-2 min-h-[58px] flex flex-wrap items-center gap-1.5">
|
||||
<div
|
||||
class="border border-[color:var(--color-bg-rule)] rounded-[4px] bg-[color:var(--color-bg-card)] px-2 py-2 min-h-[58px] flex flex-wrap items-center gap-1.5">
|
||||
@forelse ($this->tags as $tag)
|
||||
<span class="pr-tag-chip" wire:key="tag-{{ $tag }}">
|
||||
{{ $tag }}
|
||||
<button type="button" class="x" wire:click="removeTag(@js($tag))" title="{{ __('Entfernen') }}">×</button>
|
||||
<button type="button" class="x"
|
||||
wire:click="removeTag(@js($tag))"
|
||||
title="{{ __('Entfernen') }}">×</button>
|
||||
</span>
|
||||
@empty
|
||||
@if (count($this->tags) === 0)
|
||||
|
|
@ -876,28 +928,23 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</span>
|
||||
@endif
|
||||
@endforelse
|
||||
<input
|
||||
type="text"
|
||||
x-model="tagInput"
|
||||
<input type="text" x-model="tagInput"
|
||||
@keydown.enter.prevent="if (tagInput.trim()) { $wire.addTag(tagInput.trim()); tagInput = ''; }"
|
||||
@keydown.comma.prevent="if (tagInput.trim()) { $wire.addTag(tagInput.trim()); tagInput = ''; }"
|
||||
class="flex-1 min-w-[80px] border-0 bg-transparent text-[12px] text-[color:var(--color-ink)] focus:outline-none p-1"
|
||||
placeholder="{{ count($this->tags) === 0 ? '' : '+ Tag' }}"
|
||||
@disabled(count($this->tags) >= 5)
|
||||
/>
|
||||
@disabled(count($this->tags) >= 5) />
|
||||
</div>
|
||||
|
||||
@if (! empty($tagSuggestions))
|
||||
@if (!empty($tagSuggestions))
|
||||
<div>
|
||||
<div class="eyebrow muted mb-1.5" style="font-size:9.5px;">{{ __('Vorschläge') }}</div>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@foreach ($tagSuggestions as $suggestion)
|
||||
@if (! in_array($suggestion, $this->tags, true))
|
||||
<button
|
||||
type="button"
|
||||
class="pr-tag-suggest"
|
||||
wire:click="addTag(@js($suggestion))"
|
||||
>+ {{ $suggestion }}</button>
|
||||
@if (!in_array($suggestion, $this->tags, true))
|
||||
<button type="button" class="pr-tag-suggest"
|
||||
wire:click="addTag(@js($suggestion))">+
|
||||
{{ $suggestion }}</button>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
|
|
@ -919,7 +966,8 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<input type="radio" wire:model.live="publishMode" value="now" class="sr-only" />
|
||||
<span class="dot-out"></span>
|
||||
<span>
|
||||
<span class="text-[12.5px] font-semibold text-[color:var(--color-hub)] block leading-tight">
|
||||
<span
|
||||
class="text-[12.5px] font-semibold text-[color:var(--color-hub)] block leading-tight">
|
||||
{{ __('Sofort nach Freigabe') }}
|
||||
</span>
|
||||
<span class="text-[11px] text-[color:var(--color-ink-3)] block mt-0.5 leading-tight">
|
||||
|
|
@ -941,39 +989,31 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
</label>
|
||||
|
||||
@if ($publishMode === 'scheduled')
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Veröffentlichungstermin') }}</flux:label>
|
||||
<flux:input
|
||||
wire:model.live="scheduledAt"
|
||||
type="datetime-local"
|
||||
:min="now()->addMinutes(5)->format('Y-m-d\\TH:i')"
|
||||
/>
|
||||
<flux:description>{{ __('Frühestens 5 Min. in der Zukunft.') }}</flux:description>
|
||||
<flux:error name="scheduledAt" />
|
||||
</flux:field>
|
||||
@endif
|
||||
|
||||
<div class="border-t pt-3" style="border-color: var(--color-line);">
|
||||
<flux:switch
|
||||
wire:model.live="useEmbargo"
|
||||
:label="__('Sperrfrist (Embargo) setzen')"
|
||||
/>
|
||||
<p class="text-[11px] text-[color:var(--color-ink-3)] mt-1 leading-tight">
|
||||
{{ __('PM ist intern frei, aber öffentlich erst ab gewähltem Zeitpunkt sichtbar.') }}
|
||||
</p>
|
||||
|
||||
@if ($useEmbargo)
|
||||
<flux:field class="mt-3">
|
||||
<flux:label>{{ __('Sperrfrist bis') }}</flux:label>
|
||||
<flux:input
|
||||
wire:model.live="embargoAt"
|
||||
type="datetime-local"
|
||||
:min="now()->format('Y-m-d\\TH:i')"
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Datum') }}</flux:label>
|
||||
<flux:date-picker
|
||||
wire:model.live="scheduledDate"
|
||||
type="input"
|
||||
:placeholder="__('Datum wählen')"
|
||||
with-today
|
||||
/>
|
||||
<flux:error name="embargoAt" />
|
||||
<flux:error name="scheduledDate" />
|
||||
</flux:field>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Uhrzeit') }}</flux:label>
|
||||
<flux:time-picker
|
||||
wire:model.live="scheduledTime"
|
||||
type="input"
|
||||
:placeholder="__('Uhrzeit wählen')"
|
||||
/>
|
||||
<flux:error name="scheduledTime" />
|
||||
</flux:field>
|
||||
</div>
|
||||
<flux:error name="scheduledAt" />
|
||||
<p class="text-[11px] text-[color:var(--color-ink-3)] leading-tight">{{ __('Frühestens 5 Min. in der Zukunft.') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
|
@ -1005,10 +1045,12 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
|
|||
<div class="rounded-[5px] border p-3.5"
|
||||
style="background:var(--color-accent-soft);border-color:color-mix(in srgb, var(--color-accent) 50%, transparent);">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<flux:icon name="sparkles" variant="micro" class="size-3.5 text-[color:var(--color-accent-deep)]" />
|
||||
<flux:icon name="sparkles" variant="micro"
|
||||
class="size-3.5 text-[color:var(--color-accent-deep)]" />
|
||||
<span class="eyebrow accent">{{ __('Phase 2 — bald') }}</span>
|
||||
</div>
|
||||
<ul class="text-[11.5px] text-[color:var(--color-accent-deep)] leading-[1.55] list-none p-0 m-0 space-y-1">
|
||||
<ul
|
||||
class="text-[11.5px] text-[color:var(--color-accent-deep)] leading-[1.55] list-none p-0 m-0 space-y-1">
|
||||
<li>· {{ __('KI-Titel-Optimierung & KI-Lektorat live') }}</li>
|
||||
<li>· {{ __('Versionshistorie & Kommentare') }}</li>
|
||||
<li>· {{ __('Portal-Vorschau (presseecho vs. BP24)') }}</li>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -27,6 +27,9 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
#[Url(as: 'status', except: 'all')]
|
||||
public string $statusFilter = 'all';
|
||||
|
||||
#[Url(as: 'classification', except: 'all')]
|
||||
public string $classificationFilter = 'all';
|
||||
|
||||
public string $portalFilter = 'all';
|
||||
|
||||
public string $languageFilter = 'all';
|
||||
|
|
@ -74,6 +77,11 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedClassificationFilter(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedPortalFilter(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
|
|
@ -142,6 +150,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
{
|
||||
$this->search = '';
|
||||
$this->statusFilter = 'all';
|
||||
$this->classificationFilter = 'all';
|
||||
$this->portalFilter = 'all';
|
||||
$this->languageFilter = 'all';
|
||||
$this->categoryFilter = 'all';
|
||||
|
|
@ -225,6 +234,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
});
|
||||
})
|
||||
->when($this->statusFilter !== 'all', fn ($q) => $q->where('status', $this->statusFilter))
|
||||
->when($this->classificationFilter !== 'all', fn ($q) => $q->where('classification', $this->classificationFilter))
|
||||
->when($this->portalFilter !== 'all', fn ($q) => $q->where('portal', $this->portalFilter))
|
||||
->when($this->languageFilter !== 'all', fn ($q) => $q->where('language', $this->languageFilter))
|
||||
->when($this->categoryFilter !== 'all', fn ($q) => $q->where('category_id', (int) $this->categoryFilter))
|
||||
|
|
@ -472,6 +482,13 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model.live="classificationFilter" class="w-full">
|
||||
<option value="all">{{ __('Alle KI-Bewertungen') }}</option>
|
||||
@foreach (\App\Enums\PressReleaseClassification::cases() as $c)
|
||||
<option value="{{ $c->value }}">{{ __('KI: :label', ['label' => $c->label()]) }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model.live="portalFilter" class="w-full">
|
||||
<option value="all">{{ __('Alle Portale') }}</option>
|
||||
@foreach ($portalOptions as $p)
|
||||
|
|
@ -532,7 +549,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearUserFilter"
|
||||
title="{{ __('Usersuche zurücksetzen') }}"
|
||||
|
|
@ -572,7 +589,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearCompanyFilter"
|
||||
title="{{ __('Firmensuche zurücksetzen') }}"
|
||||
|
|
@ -618,7 +635,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearContactFilter"
|
||||
title="{{ __('Kontaktsuche zurücksetzen') }}"
|
||||
|
|
@ -630,6 +647,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
@php
|
||||
$hasAnyFilter = $search !== ''
|
||||
|| $statusFilter !== 'all'
|
||||
|| $classificationFilter !== 'all'
|
||||
|| $portalFilter !== 'all'
|
||||
|| $languageFilter !== 'all'
|
||||
|| $categoryFilter !== 'all'
|
||||
|
|
@ -670,6 +688,21 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
</span>
|
||||
@endif
|
||||
|
||||
@if ($classificationFilter !== 'all')
|
||||
@php $classificationEnum = \App\Enums\PressReleaseClassification::tryFrom($classificationFilter); @endphp
|
||||
<span class="active-chip">
|
||||
<span>{{ __('KI-Bewertung') }}:
|
||||
<strong>{{ $classificationEnum?->label() ?? $classificationFilter }}</strong></span>
|
||||
<button type="button" class="x" wire:click="$set('classificationFilter', 'all')"
|
||||
aria-label="{{ __('Filter entfernen') }}">
|
||||
<svg width="8" height="8" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M3 3l6 6M9 3l-6 6" stroke="currentColor" stroke-width="1.6"
|
||||
stroke-linecap="round" />
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@if ($portalFilter !== 'all')
|
||||
@php $portalEnum = \App\Enums\Portal::tryFrom($portalFilter); @endphp
|
||||
<span class="active-chip">
|
||||
|
|
@ -834,6 +867,26 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
<flux:table.cell>
|
||||
<div class="flex items-center gap-1.5 flex-wrap">
|
||||
<span class="badge {{ $badgeClass }} dot">{{ $pr->status->label() }}</span>
|
||||
@if ($pr->classification)
|
||||
@php
|
||||
$kiBadge = match ($pr->classification) {
|
||||
\App\Enums\PressReleaseClassification::Green => 'ok',
|
||||
\App\Enums\PressReleaseClassification::Yellow => 'warn',
|
||||
\App\Enums\PressReleaseClassification::Red => 'err',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge {{ $kiBadge }}" title="{{ __('KI-Bewertung') }}">{{ __('KI: :label', ['label' => $pr->classification->label()]) }}</span>
|
||||
@endif
|
||||
@if (! is_null($pr->content_score) && $pr->content_tier)
|
||||
@php
|
||||
$tierBadge = match ($pr->content_tier) {
|
||||
\App\Enums\PressReleaseContentTier::Hochwertig => 'ok',
|
||||
\App\Enums\PressReleaseContentTier::Geprueft => 'hub',
|
||||
\App\Enums\PressReleaseContentTier::Standard => 'muted',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge {{ $tierBadge }}" title="{{ __('Content-Score') }}">{{ $pr->content_score }} · {{ $pr->content_tier->label() }}</span>
|
||||
@endif
|
||||
@if ($pr->status === \App\Enums\PressReleaseStatus::Review)
|
||||
<flux:modal.trigger name="confirm-index-publish-{{ $pr->id }}">
|
||||
<button type="button" class="inline-action"
|
||||
|
|
@ -903,13 +956,13 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
@if ($status === 'review' && $pr->scheduled_at && $pr->scheduled_at->isFuture())
|
||||
<div class="mt-1 inline-flex items-center gap-1 text-[10.5px] font-mono tabular-nums text-[color:var(--color-accent-deep)]">
|
||||
<flux:icon.calendar variant="micro" class="size-3" />
|
||||
<span>{{ __('geplant') }} · {{ $pr->scheduled_at->format('d.m. H:i') }}</span>
|
||||
<span>{{ __('geplant') }} · {{ $pr->scheduledAtLocal()->format('d.m. H:i') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if ($pr->embargo_at && $pr->embargo_at->isFuture())
|
||||
<div class="mt-1 inline-flex items-center gap-1 text-[10.5px] font-mono tabular-nums text-[color:var(--color-accent-deep)]">
|
||||
<flux:icon.lock-closed variant="micro" class="size-3" />
|
||||
<span>{{ __('Embargo bis') }} {{ $pr->embargo_at->format('d.m.') }}</span>
|
||||
<span>{{ __('Embargo bis') }} {{ $pr->embargoAtLocal()->format('d.m.') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
|
|
@ -921,10 +974,10 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
|
|||
|
||||
<flux:table.cell>
|
||||
<div class="flex items-center gap-1">
|
||||
<flux:button size="sm" variant="ghost" icon="eye"
|
||||
<flux:button size="sm" variant="filled" icon="eye"
|
||||
href="{{ route('admin.press-releases.show', $pr->id) }}" wire:navigate
|
||||
title="{{ __('Ansehen') }}" />
|
||||
<flux:button size="sm" variant="ghost" icon="pencil"
|
||||
<flux:button size="sm" variant="filled" icon="pencil"
|
||||
href="{{ route('admin.press-releases.edit', $pr->id) }}" wire:navigate
|
||||
title="{{ __('Bearbeiten') }}" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Models\PressRelease;
|
||||
use App\Services\PressRelease\BlacklistViolationException;
|
||||
use App\Services\PressRelease\PressReleaseCoverImage;
|
||||
use App\Services\PressRelease\PressReleaseService;
|
||||
use Flux\Flux;
|
||||
use Livewire\Attributes\Layout;
|
||||
|
|
@ -81,20 +82,29 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
->orderBy('first_name')
|
||||
->select(['contacts.id', 'contacts.company_id', 'contacts.first_name', 'contacts.last_name', 'contacts.responsibility', 'contacts.email', 'contacts.phone']),
|
||||
'statusLogs.changedBy:id,name',
|
||||
'kiAudits',
|
||||
])
|
||||
->findOrFail($this->id);
|
||||
|
||||
$latestClassification = $pr->kiAudits
|
||||
->firstWhere('type', \App\Models\KiAudit::TYPE_CLASSIFICATION);
|
||||
|
||||
$latestRejection = null;
|
||||
if ($pr->status->value === 'rejected') {
|
||||
$latestRejection = $pr->statusLogs
|
||||
->firstWhere(fn ($log) => $log->to_status?->value === 'rejected');
|
||||
}
|
||||
|
||||
$cover = app(PressReleaseCoverImage::class);
|
||||
|
||||
return [
|
||||
'pr' => $pr,
|
||||
'statusLogs' => $pr->statusLogs,
|
||||
'contacts' => $pr->contacts,
|
||||
'latestClassification' => $latestClassification,
|
||||
'latestRejection' => $latestRejection,
|
||||
'coverUrl' => $cover->coverUrl($pr, 'cover'),
|
||||
'coverIsPlaceholder' => $cover->coverIsPlaceholder($pr),
|
||||
'categoryName' => $pr->category?->translations->firstWhere('locale', 'de')?->name
|
||||
?? $pr->category?->translations->first()?->name
|
||||
?? '–',
|
||||
|
|
@ -128,6 +138,26 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Content · Pressemitteilung') }}</span>
|
||||
<span @class(['badge', $statusClass])>{{ $pr->status->label() }}</span>
|
||||
@if ($pr->classification)
|
||||
@php
|
||||
$kiBadgeClass = match ($pr->classification) {
|
||||
\App\Enums\PressReleaseClassification::Green => 'ok',
|
||||
\App\Enums\PressReleaseClassification::Yellow => 'warn',
|
||||
\App\Enums\PressReleaseClassification::Red => 'err',
|
||||
};
|
||||
@endphp
|
||||
<span @class(['badge', $kiBadgeClass])>{{ __('KI: :label', ['label' => $pr->classification->label()]) }}</span>
|
||||
@endif
|
||||
@if (! is_null($pr->content_score) && $pr->content_tier)
|
||||
@php
|
||||
$tierBadge = match ($pr->content_tier) {
|
||||
\App\Enums\PressReleaseContentTier::Hochwertig => 'ok',
|
||||
\App\Enums\PressReleaseContentTier::Geprueft => 'hub',
|
||||
\App\Enums\PressReleaseContentTier::Standard => 'muted',
|
||||
};
|
||||
@endphp
|
||||
<span @class(['badge', $tierBadge])>{{ __('Score :score · :tier', ['score' => $pr->content_score, 'tier' => $pr->content_tier->label()]) }}</span>
|
||||
@endif
|
||||
<span class="badge hub">{{ strtoupper($pr->language) }}</span>
|
||||
<span class="badge hub">{{ $pr->portal->label() }}</span>
|
||||
</div>
|
||||
|
|
@ -152,15 +182,31 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="pencil" href="{{ route('admin.press-releases.edit', $pr->id) }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="pencil" href="{{ route('admin.press-releases.edit', $pr->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.press-releases.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.press-releases.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ============== TITELBILD (Hero) ============== --}}
|
||||
{{-- Harte Obergrenze 1280x580 px: Container deckelt Breite und Seitenverhältnis,
|
||||
damit das Bild auf großen Screens nicht über die Detailgröße hinauswächst. --}}
|
||||
<article class="panel overflow-hidden mx-auto w-full max-w-[1280px]">
|
||||
<div class="relative aspect-[1280/580] w-full">
|
||||
<img src="{{ $coverUrl }}" alt="{{ $pr->title }}"
|
||||
class="absolute inset-0 h-full w-full object-cover" loading="lazy" />
|
||||
</div>
|
||||
@if ($coverIsPlaceholder)
|
||||
<div class="flex items-center gap-2 border-t border-[color:var(--color-bg-rule)] px-5 py-2.5 text-[12px] text-[color:var(--color-ink-3)]">
|
||||
<flux:icon.photo variant="micro" class="size-3.5" />
|
||||
<span>{{ __('Platzhalter-Titelbild (kein eigenes Bild hochgeladen).') }}</span>
|
||||
</div>
|
||||
@endif
|
||||
</article>
|
||||
|
||||
{{-- ============== REJECTION-HINWEIS ============== --}}
|
||||
@if ($pr->status === \App\Enums\PressReleaseStatus::Rejected && $latestRejection)
|
||||
<article class="panel" style="border-color:var(--color-err); border-left-width:3px;">
|
||||
|
|
@ -205,10 +251,16 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
</div>
|
||||
<div class="flex-1 min-w-[220px] text-[13px] text-[color:var(--color-ink-2)]">
|
||||
<p class="m-0">{{ __('Diese PM wartet auf eine redaktionelle Entscheidung.') }}</p>
|
||||
@if ($latestClassification && $latestClassification->reason)
|
||||
<p class="m-0 mt-1 text-[12px] text-[color:var(--color-ink-3)]">
|
||||
<strong class="text-[color:var(--color-ink-2)]">{{ __('KI-Hinweis') }}:</strong>
|
||||
{{ $latestClassification->reason }}
|
||||
</p>
|
||||
@endif
|
||||
@if ($pr->scheduled_at)
|
||||
<p class="m-0 mt-1 text-[12px] text-[color:var(--color-ink-3)]">
|
||||
<flux:icon.calendar variant="micro" class="inline-block size-3.5 -mt-0.5 mr-0.5" />
|
||||
{{ __('Geplante Veröffentlichung: :date', ['date' => $pr->scheduled_at->format('d.m.Y H:i')]) }}
|
||||
{{ __('Geplante Veröffentlichung: :date', ['date' => $pr->scheduledAtLocal()->format('d.m.Y H:i')]) }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
|
|
@ -243,7 +295,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
@if ($pr->embargo_at && $pr->embargo_at->isFuture())
|
||||
<p class="m-0 mt-1 text-[12px] text-[color:var(--color-ink-3)]">
|
||||
<flux:icon.lock-closed variant="micro" class="inline-block size-3.5 -mt-0.5 mr-0.5" />
|
||||
{{ __('Sperrfrist bis: :date', ['date' => $pr->embargo_at->format('d.m.Y H:i')]) }}
|
||||
{{ __('Sperrfrist bis: :date', ['date' => $pr->embargoAtLocal()->format('d.m.Y H:i')]) }}
|
||||
</p>
|
||||
@endif
|
||||
@if ($pr->hits > 0)
|
||||
|
|
@ -254,7 +306,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
@endif
|
||||
</div>
|
||||
<flux:modal.trigger name="confirm-show-archive">
|
||||
<flux:button type="button" variant="ghost">{{ __('Archivieren') }}</flux:button>
|
||||
<flux:button type="button" variant="filled">{{ __('Archivieren') }}</flux:button>
|
||||
</flux:modal.trigger>
|
||||
</div>
|
||||
</article>
|
||||
|
|
@ -266,7 +318,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Zugeordnete Pressekontakte') }}</span>
|
||||
@if ($pr->company)
|
||||
<flux:button size="sm" variant="ghost" icon="building-office" href="{{ route('admin.companies.show', $pr->company->id) }}" wire:navigate>
|
||||
<flux:button size="sm" variant="filled" icon="building-office" href="{{ route('admin.companies.show', $pr->company->id) }}" wire:navigate>
|
||||
{{ __('Firma') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
|
|
@ -340,7 +392,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Geplant') }}</div>
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||||
{{ $pr->scheduled_at->format('d.m.Y H:i') }}
|
||||
{{ $pr->scheduledAtLocal()->format('d.m.Y H:i') }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
@ -348,7 +400,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
|||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">{{ __('Sperrfrist bis') }}</div>
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] mt-1">
|
||||
{{ $pr->embargo_at->format('d.m.Y H:i') }}
|
||||
{{ $pr->embargoAtLocal()->format('d.m.Y H:i') }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ new #[Layout('components.layouts.app'), Title('Performance Reports')] class exte
|
|||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter') }}</span>
|
||||
<flux:button size="sm" variant="ghost" icon="arrow-path" wire:click="resetFilters">
|
||||
<flux:button size="sm" variant="filled" icon="arrow-path" wire:click="resetFilters">
|
||||
{{ __('Filter zurücksetzen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ new #[Layout('components.layouts.app'), Title('Neue Rolle')] class extends Compo
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.roles.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.roles.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -143,7 +143,7 @@ new #[Layout('components.layouts.app'), Title('Neue Rolle')] class extends Compo
|
|||
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.roles.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.roles.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary">
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ new #[Layout('components.layouts.app'), Title('Rolle bearbeiten')] class extends
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.roles.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.roles.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -178,7 +178,7 @@ new #[Layout('components.layouts.app'), Title('Rolle bearbeiten')] class extends
|
|||
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.roles.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.roles.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary">
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ new #[Layout('components.layouts.app'), Title('Rollen & Rechte')] class extends
|
|||
|
||||
<flux:table.cell>
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.roles.edit'))
|
||||
<flux:button size="sm" variant="ghost" icon="pencil" href="{{ route('admin.roles.edit', $role->id) }}" wire:navigate />
|
||||
<flux:button size="sm" variant="filled" icon="pencil" href="{{ route('admin.roles.edit', $role->id) }}" wire:navigate />
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
|
|||
@if ($search || $activeFilter !== 'all' || $portalFilter !== 'all' || $roleFilter !== 'all' || $qualityFilter !== 'all' || $permissionFilter !== 'all')
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="badge hub dot">{{ __('Filter aktiv') }}</span>
|
||||
<flux:button size="sm" variant="ghost" icon="arrow-path" type="button" wire:click="resetFilters">
|
||||
<flux:button size="sm" variant="filled" icon="arrow-path" type="button" wire:click="resetFilters">
|
||||
{{ __('Zurücksetzen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -464,14 +464,14 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
|
|||
|
||||
<flux:table.cell>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<flux:button size="sm" variant="ghost" icon="eye"
|
||||
<flux:button size="sm" variant="filled" icon="eye"
|
||||
wire:click="showUserDetails({{ $user->id }})" />
|
||||
<flux:button size="sm" variant="ghost" icon="pencil"
|
||||
<flux:button size="sm" variant="filled" icon="pencil"
|
||||
href="{{ route('admin.users.edit', $user->id) }}" wire:navigate />
|
||||
@if($canLoginAsUser)
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="user"
|
||||
square
|
||||
type="button"
|
||||
|
|
@ -643,7 +643,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
|
|||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
<flux:modal.close>
|
||||
<flux:button variant="ghost">{{ __('Schließen') }}</flux:button>
|
||||
<flux:button variant="filled">{{ __('Schließen') }}</flux:button>
|
||||
</flux:modal.close>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -663,7 +663,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
|
|||
@if($selectedUser->published_press_releases_count > 0)
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="arrow-top-right-on-square"
|
||||
class="mt-2"
|
||||
href="{{ route('admin.press-releases.index', ['user' => $selectedUser->id, 'status' => \App\Enums\PressReleaseStatus::Published->value]) }}"
|
||||
|
|
@ -898,7 +898,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
|
|||
</div>
|
||||
</div>
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.edit'))
|
||||
<flux:button size="sm" variant="ghost" icon="pencil" href="{{ route('admin.contacts.edit', $contact->id) }}" wire:navigate>
|
||||
<flux:button size="sm" variant="filled" icon="pencil" href="{{ route('admin.contacts.edit', $contact->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer anlegen')] class extends
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -365,7 +365,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer anlegen')] class extends
|
|||
<option value="owner">{{ __('Owner') }}</option>
|
||||
</flux:select>
|
||||
|
||||
<flux:button size="sm" variant="ghost" icon="x-mark"
|
||||
<flux:button size="sm" variant="filled" icon="x-mark"
|
||||
wire:click="removeLinkedCompany({{ $company->id }})">
|
||||
{{ __('Entfernen') }}
|
||||
</flux:button>
|
||||
|
|
@ -439,7 +439,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer anlegen')] class extends
|
|||
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary" icon="check">
|
||||
|
|
|
|||
|
|
@ -739,7 +739,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer bearbeiten')] class exte
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -1035,7 +1035,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer bearbeiten')] class exte
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearCompanyLookup"
|
||||
title="{{ __('Firmensuche zurücksetzen') }}"
|
||||
|
|
@ -1056,7 +1056,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer bearbeiten')] class exte
|
|||
<option value="owner">{{ __('Owner') }}</option>
|
||||
</flux:select>
|
||||
|
||||
<flux:button type="button" variant="ghost" icon="x-mark" wire:click="removeLinkedCompany({{ $company->id }})">
|
||||
<flux:button type="button" variant="filled" icon="x-mark" wire:click="removeLinkedCompany({{ $company->id }})">
|
||||
{{ __('Entfernen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
|
@ -1113,7 +1113,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer bearbeiten')] class exte
|
|||
<flux:button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
wire:click="clearContactLookup"
|
||||
title="{{ __('Kontaktsuche zurücksetzen') }}"
|
||||
|
|
@ -1128,7 +1128,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer bearbeiten')] class exte
|
|||
</flux:text>
|
||||
<flux:button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
variant="filled"
|
||||
icon="x-mark"
|
||||
type="button"
|
||||
wire:click="removeLinkedContact({{ $contactForm['id'] }})"
|
||||
|
|
@ -1230,7 +1230,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer bearbeiten')] class exte
|
|||
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary" icon="check">
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer anzeigen')] class extend
|
|||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.users.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
<flux:button variant="primary" icon="pencil" href="{{ route('admin.users.edit', $user->id) }}" wire:navigate>
|
||||
|
|
@ -221,7 +221,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer anzeigen')] class extend
|
|||
<div class="flex shrink-0 items-center gap-2">
|
||||
<span class="badge hub">{{ $contact->portal?->label() ?? '—' }}</span>
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.edit'))
|
||||
<flux:button size="sm" variant="ghost" icon="pencil"
|
||||
<flux:button size="sm" variant="filled" icon="pencil"
|
||||
href="{{ route('admin.contacts.edit', $contact->id) }}"
|
||||
wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ new #[Layout('components.layouts.app'), Title('Benutzertabelle')] class extends
|
|||
|
||||
<flux:table.cell>
|
||||
<flux:dropdown>
|
||||
<flux:button variant="ghost" size="sm" icon="ellipsis-horizontal" inset="top bottom"></flux:button>
|
||||
<flux:button variant="filled" size="sm" icon="ellipsis-horizontal" inset="top bottom"></flux:button>
|
||||
<flux:menu>
|
||||
<flux:menu.item icon="plus">New post</flux:menu.item>
|
||||
<flux:menu.separator />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue