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>
189 lines
6.9 KiB
PHP
189 lines
6.9 KiB
PHP
<?php
|
|
|
|
use App\Services\Admin\AdminPerformanceCache;
|
|
use Illuminate\Database\Query\Builder;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\Rule;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Locked;
|
|
use Livewire\Attributes\Title;
|
|
use Livewire\Volt\Component;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
|
|
new #[Layout('components.layouts.app'), Title('Rolle bearbeiten')] class extends Component
|
|
{
|
|
#[Locked]
|
|
public int $id;
|
|
|
|
public string $name = '';
|
|
|
|
public array $permissions = [];
|
|
|
|
public string $guardName = 'web';
|
|
|
|
public bool $isSystemRole = false;
|
|
|
|
public function mount(int $id): void
|
|
{
|
|
$this->id = $id;
|
|
|
|
$role = Role::query()
|
|
->with('permissions:id,name,guard_name')
|
|
->findOrFail($id);
|
|
|
|
$this->name = $role->name;
|
|
$this->guardName = $role->guard_name;
|
|
$this->permissions = $role->permissions
|
|
->pluck('name')
|
|
->values()
|
|
->all();
|
|
$this->isSystemRole = in_array($role->name, ['admin', 'editor', 'customer', 'api-only'], true);
|
|
}
|
|
|
|
public function save(): void
|
|
{
|
|
$validated = $this->validate([
|
|
'name' => [
|
|
'required',
|
|
'min:3',
|
|
'max:50',
|
|
Rule::unique('roles', 'name')
|
|
->ignore($this->id)
|
|
->where(fn (Builder $query) => $query->where('guard_name', $this->guardName)),
|
|
],
|
|
'permissions' => ['array'],
|
|
'permissions.*' => [
|
|
'string',
|
|
Rule::exists('permissions', 'name')
|
|
->where(fn (Builder $query) => $query->where('guard_name', $this->guardName)),
|
|
],
|
|
]);
|
|
|
|
$role = Role::query()->findOrFail($this->id);
|
|
$role->name = $validated['name'];
|
|
$role->save();
|
|
$role->syncPermissions($validated['permissions'] ?? []);
|
|
|
|
session()->flash('success', 'Rolle und Berechtigungen erfolgreich aktualisiert.');
|
|
$this->redirect(route('admin.roles.index'), navigate: true);
|
|
}
|
|
|
|
public function with(): array
|
|
{
|
|
return [
|
|
'permissionGroups' => $this->permissionGroups(),
|
|
];
|
|
}
|
|
|
|
private function permissionGroups(): Collection
|
|
{
|
|
$cache = app(AdminPerformanceCache::class);
|
|
|
|
return $cache->remember($cache->permissionGroupsKey($this->guardName), AdminPerformanceCache::OptionsTtl, fn () => Permission::query()
|
|
->where('guard_name', $this->guardName)
|
|
->orderBy('name')
|
|
->get(['name'])
|
|
->groupBy(function (Permission $permission): string {
|
|
$prefix = Str::contains($permission->name, ':')
|
|
? Str::before($permission->name, ':')
|
|
: $permission->name;
|
|
|
|
return Str::headline(str_replace(['-', '_'], ' ', $prefix));
|
|
})
|
|
->map(fn ($group) => $group->values())
|
|
->sortKeys());
|
|
}
|
|
}; ?>
|
|
|
|
<form wire:submit="save" class="space-y-8">
|
|
{{-- ============== PAGE HEADER ============== --}}
|
|
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
|
<div class="min-w-0">
|
|
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
|
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
|
<span class="eyebrow muted">{{ __('Administration · Sicherheit · Rollen') }}</span>
|
|
<span class="badge hub">ID #{{ $id }}</span>
|
|
<span class="badge hub">{{ __('Guard') }}: {{ $guardName }}</span>
|
|
@if ($isSystemRole)
|
|
<span class="badge hub dot">{{ __('Systemrolle') }}</span>
|
|
@endif
|
|
</div>
|
|
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
|
{{ __('Rolle bearbeiten') }}
|
|
</h1>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2 flex-shrink-0">
|
|
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.roles.index') }}" wire:navigate>
|
|
{{ __('Zurück') }}
|
|
</flux:button>
|
|
</div>
|
|
</header>
|
|
|
|
@if ($isSystemRole)
|
|
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-start gap-3
|
|
bg-[color:var(--color-warn-soft)] border-[color:var(--color-warn)]/30 text-[color:var(--color-ink-2)]">
|
|
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0 mt-0.5 text-[color:var(--color-accent-deep)]" />
|
|
<div class="flex-1">
|
|
{{ __('Hinweis: Diese Rolle ist Teil des Basis-Setups. Aenderungen wirken sich direkt auf den Admin-Zugriff aus.') }}
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<article class="panel">
|
|
<div class="panel-head">
|
|
<span class="section-eyebrow">{{ __('Basis-Informationen') }}</span>
|
|
</div>
|
|
<div class="p-5">
|
|
<flux:field>
|
|
<flux:label>
|
|
{{ __('Technischer Name') }}
|
|
<span class="text-[color:var(--color-err)]">*</span>
|
|
</flux:label>
|
|
<flux:input wire:model="name" />
|
|
<flux:error name="name" />
|
|
</flux:field>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="panel">
|
|
<div class="panel-head">
|
|
<span class="section-eyebrow">{{ __('Berechtigungen') }}</span>
|
|
</div>
|
|
<div class="p-5 space-y-6">
|
|
@forelse ($permissionGroups as $groupName => $permissionsInGroup)
|
|
<div>
|
|
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-3">
|
|
{{ $groupName }}
|
|
</div>
|
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
@foreach ($permissionsInGroup as $permission)
|
|
<flux:checkbox
|
|
wire:model="permissions"
|
|
value="{{ $permission['name'] }}"
|
|
label="{{ $permission['name'] }}"
|
|
/>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<p class="text-[12.5px] text-[color:var(--color-ink-3)] m-0">{{ __('Keine Berechtigungen fuer diesen Guard vorhanden.') }}</p>
|
|
@endforelse
|
|
|
|
<flux:error name="permissions" />
|
|
</div>
|
|
</article>
|
|
|
|
<article class="panel">
|
|
<div class="p-5 flex justify-end gap-3">
|
|
<flux:button variant="filled" href="{{ route('admin.roles.index') }}" wire:navigate>
|
|
{{ __('Abbrechen') }}
|
|
</flux:button>
|
|
<flux:button type="submit" variant="primary">
|
|
{{ __('Aenderungen speichern') }}
|
|
</flux:button>
|
|
</div>
|
|
</article>
|
|
</form>
|