presseportale/resources/views/livewire/admin/legal-requests/show.blade.php
Kevin Adametz 2a9be7a9af T10: DSA-Meldeformulare mit URL-Feld, Vorgangsnummer und Eingangsbestaetigung
Zwei eigenstaendige Formulare unter /melden/rechtswidrig (DSA Art. 16:
URL, Name, E-Mail, Begruendung, Wahrheitsversicherung) und
/melden/kontaktdaten (DSGVO-Antrag) auf allen drei Domains. Gemeldete
URLs werden automatisch der passenden veroeffentlichten PM zugeordnet;
ohne Treffer bleibt die URL im payload. Jede Meldung erhaelt eine
Vorgangsnummer (M-{id}, analog K-{id} der Kuendigung), die meldende
Person unverzueglich eine Eingangsbestaetigung (DSA Art. 16 Abs. 4) und
das Melde-Postfach einen internen Hinweis — auch im bestehenden
Per-PM-Flow. Admin-Queue zeigt Vorgangsnummer, Name und gemeldete URL;
/melden verlinkt die neuen Formulare als ersten Weg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:35:30 +00:00

212 lines
9.3 KiB
PHP

<?php
use App\Enums\LegalRequestStatus;
use App\Models\LegalRequest;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;
new #[Layout('components.layouts.app'), Title('Anfrage · Recht & Compliance')] class extends Component
{
#[Locked]
public int $id;
public string $adminNote = '';
public string $notification = '';
public function mount(int $id): void
{
$this->id = $id;
$this->adminNote = (string) ($this->request()->admin_note ?? '');
}
public function markInProgress(): void
{
$this->request()->update(['status' => LegalRequestStatus::InProgress->value]);
$this->notification = __('Status auf „In Bearbeitung" gesetzt.');
}
public function resolve(): void
{
$this->finish(LegalRequestStatus::Resolved, __('Anfrage als erledigt markiert.'));
}
public function reject(): void
{
$this->finish(LegalRequestStatus::Rejected, __('Anfrage abgelehnt.'));
}
public function reopen(): void
{
$this->request()->update([
'status' => LegalRequestStatus::Open->value,
'resolved_by_user_id' => null,
'resolved_at' => null,
]);
$this->notification = __('Anfrage wieder geöffnet.');
}
private function finish(LegalRequestStatus $status, string $message): void
{
$this->request()->update([
'status' => $status->value,
'admin_note' => $this->adminNote !== '' ? $this->adminNote : null,
'resolved_by_user_id' => auth()->id(),
'resolved_at' => now(),
]);
$this->notification = $message;
}
private function request(): LegalRequest
{
return LegalRequest::query()->with(['pressRelease', 'resolver'])->findOrFail($this->id);
}
public function with(): array
{
return ['request' => $this->request()];
}
public function statusBadgeClass(LegalRequestStatus $status): string
{
return match ($status) {
LegalRequestStatus::Open => 'warn',
LegalRequestStatus::InProgress => 'hub',
LegalRequestStatus::Resolved => 'ok',
LegalRequestStatus::Rejected => 'err',
};
}
}; ?>
<div class="space-y-8">
{{-- ============== PAGE HEADER ============== --}}
<x-portal.page-header
size="sm"
:badge="__('Admin Backend')"
:eyebrow="__('Recht & Compliance · Anfrage')"
:title="$request->type->label()"
>
<x-slot:meta>
<span class="badge dot {{ $this->statusBadgeClass($request->status) }}">{{ $request->status->label() }}</span>
</x-slot:meta>
<x-slot:actions>
<flux:button icon="arrow-left" variant="filled" href="{{ route('admin.legal-requests.index') }}" wire:navigate>
{{ __('Zurück') }}
</flux:button>
</x-slot:actions>
</x-portal.page-header>
@if ($notification)
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 3000)" x-show="show" x-transition
class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
{{ $notification }}
</div>
@endif
<div class="grid gap-6 xl:grid-cols-3">
{{-- ============== ANFRAGE ============== --}}
<article class="panel xl:col-span-2">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Anliegen') }}</span>
</div>
<div class="p-5 space-y-4">
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-4 text-[13px] text-[color:var(--color-ink)] whitespace-pre-line">
{{ $request->message ?: __('Keine Nachricht hinterlegt.') }}
</div>
<dl class="grid gap-3 sm:grid-cols-2 text-[12.5px]">
<div>
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Vorgangsnummer') }}</dt>
<dd class="text-[color:var(--color-ink)] font-mono">{{ $request->reference() }}</dd>
</div>
@if ($request->requesterName())
<div>
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Name') }}</dt>
<dd class="text-[color:var(--color-ink)]">{{ $request->requesterName() }}</dd>
</div>
@endif
<div>
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Absender') }}</dt>
<dd class="text-[color:var(--color-ink)] break-all">
@if ($request->requester_email)
<a href="mailto:{{ $request->requester_email }}"
class="text-[color:var(--color-hub)] underline underline-offset-2">{{ $request->requester_email }}</a>
@else
{{ __('anonym') }}
@endif
</dd>
</div>
<div>
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Eingegangen') }}</dt>
<dd class="text-[color:var(--color-ink)]">{{ $request->created_at?->format('d.m.Y H:i') ?? '-' }}</dd>
</div>
<div class="sm:col-span-2">
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Pressemitteilung') }}</dt>
<dd class="text-[color:var(--color-ink)]">
@if ($request->pressRelease)
{{ $request->pressRelease->title }}
@if (\Illuminate\Support\Facades\Route::has('admin.press-releases.show'))
<a href="{{ route('admin.press-releases.show', $request->pressRelease->id) }}" wire:navigate
class="ml-2 text-[12px] text-[color:var(--color-hub)] underline underline-offset-2">{{ __('öffnen') }}</a>
@endif
@elseif ($request->reportedUrl())
{{ __('Keine PM zugeordnet — gemeldete URL:') }}
<a href="{{ $request->reportedUrl() }}" target="_blank" rel="noopener"
class="text-[color:var(--color-hub)] underline underline-offset-2 break-all">{{ $request->reportedUrl() }}</a>
@else
{{ __('PM wurde gelöscht') }}
@endif
</dd>
</div>
</dl>
</div>
</article>
{{-- ============== BEARBEITUNG ============== --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Bearbeitung') }}</span>
</div>
<div class="p-5 space-y-4">
<flux:field>
<flux:textarea wire:model="adminNote" :label="__('Interne Notiz')" rows="4"
:placeholder="__('Wie wurde die Anfrage bearbeitet?')" />
</flux:field>
@if ($request->resolver)
<p class="text-[12px] text-[color:var(--color-ink-3)]">
{{ __('Zuletzt bearbeitet von :name am :date', [
'name' => $request->resolver->name,
'date' => $request->resolved_at?->format('d.m.Y H:i') ?? '-',
]) }}
</p>
@endif
<div class="flex flex-col gap-2 pt-2 border-t border-[color:var(--color-bg-rule)]">
@if ($request->status->isOpen())
@if ($request->status !== \App\Enums\LegalRequestStatus::InProgress)
<flux:button variant="filled" icon="clock" wire:click="markInProgress">
{{ __('In Bearbeitung nehmen') }}
</flux:button>
@endif
<flux:button variant="primary" icon="check" wire:click="resolve">
{{ __('Als erledigt markieren') }}
</flux:button>
<flux:button variant="danger" icon="x-mark" wire:click="reject">
{{ __('Ablehnen') }}
</flux:button>
@else
<flux:button variant="filled" icon="arrow-path" wire:click="reopen">
{{ __('Wieder öffnen') }}
</flux:button>
@endif
</div>
</div>
</article>
</div>
</div>