117 lines
7.0 KiB
PHP
117 lines
7.0 KiB
PHP
<div class="mx-auto max-w-[1120px] space-y-6">
|
|
<div class="animate-rise">
|
|
<h1 class="text-2xl font-bold tracking-tight text-ink">{{ __('roles.title') }}</h1>
|
|
<p class="mt-1 max-w-[70ch] text-sm text-muted">{{ __('roles.subtitle') }}</p>
|
|
</div>
|
|
|
|
{{-- Neue Rolle.
|
|
|
|
Vorher stand das Feld auf `flex-1` neben einem Knopf ohne `shrink-0`,
|
|
und beide auf `items-end` — bei schmalem Fenster schob der Hinweistext
|
|
unter dem Feld den Knopf aus der Karte heraus. Jetzt ein Raster mit
|
|
zwei Spuren: das Feld nimmt, was übrig ist, der Knopf behält seine
|
|
Breite, und der Hinweis steht UNTER der Reihe statt in ihr. --}}
|
|
<form wire:submit="create" class="rounded-lg border border-line bg-surface p-6 shadow-xs animate-rise">
|
|
<div class="grid gap-3 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-end">
|
|
<x-ui.input name="newRole" wire:model="newRole" :label="__('roles.new_label')" />
|
|
<x-ui.button type="submit" variant="primary" class="shrink-0"
|
|
wire:loading.attr="disabled" wire:target="create">
|
|
{{ __('roles.create') }}
|
|
</x-ui.button>
|
|
</div>
|
|
<p class="mt-2 text-xs text-muted">{{ __('roles.new_hint') }}</p>
|
|
@error('newRole')<p class="mt-1.5 text-xs text-danger">{{ $message }}</p>@enderror
|
|
</form>
|
|
|
|
{{-- Eine Karte je Rolle, zugeklappt.
|
|
|
|
Sechs Rollen mal einundzwanzig Rechte waren über hundert Kästchen
|
|
untereinander — man scrollte an der Rolle vorbei, die man ändern
|
|
wollte. Zugeklappt ist die Seite sechs Zeilen lang, und die Zeile sagt
|
|
schon, was sie enthält: wie viele Rechte, wie viele Menschen. --}}
|
|
<div class="space-y-3">
|
|
@foreach ($roles as $role)
|
|
@php
|
|
$isProtected = $role->name === $protectedRole;
|
|
// NICHT $granted nennen: das ist die Eigenschaft des
|
|
// Bauteils, und in der zweiten Runde der Schleife wäre sie
|
|
// eine Zahl statt der Liste.
|
|
$grantedCount = count($granted[$role->id] ?? []);
|
|
@endphp
|
|
<div wire:key="role-{{ $role->id }}" x-data="{ open: false }"
|
|
class="overflow-hidden rounded-lg border border-line bg-surface shadow-xs animate-rise">
|
|
|
|
{{-- Die Kopfzeile IST der Schalter. Ein eigener Pfeil-Knopf
|
|
daneben wäre ein zweites Ziel für dieselbe Absicht. --}}
|
|
<button type="button" x-on:click="open = ! open" :aria-expanded="open"
|
|
class="flex w-full flex-wrap items-center justify-between gap-3 px-6 py-4 text-left transition-colors hover:bg-surface-hover">
|
|
<span class="flex min-w-0 flex-wrap items-center gap-2">
|
|
{{-- chevron-down gedreht: der Satz kennt kein chevron-right, und
|
|
eine zweite Ikone für dieselbe Sache wäre eine zu viel. --}}
|
|
<x-ui.icon name="chevron-down" class="size-4 shrink-0 text-muted transition-transform"
|
|
x-bind:class="open ? '' : '-rotate-90'" />
|
|
<span class="font-semibold text-ink">{{ $role->name }}</span>
|
|
@if ($isProtected)
|
|
<span class="rounded-pill border border-warning-border bg-warning-bg px-2.5 py-0.5 text-xs font-medium text-warning">{{ __('roles.locked_badge') }}</span>
|
|
@endif
|
|
@if (in_array($role->name, $ownRoles, true))
|
|
<span class="rounded-pill border border-info-border bg-info-bg px-2.5 py-0.5 text-xs font-medium text-info">{{ __('roles.your_role') }}</span>
|
|
@endif
|
|
</span>
|
|
|
|
<span class="flex shrink-0 items-center gap-3 text-xs text-muted">
|
|
<span>{{ __('roles.granted_count', ['granted' => $grantedCount, 'total' => count($permissions)]) }}</span>
|
|
<span aria-hidden="true">·</span>
|
|
<span>{{ trans_choice('roles.holders', $role->users_count, ['count' => $role->users_count]) }}</span>
|
|
</span>
|
|
</button>
|
|
|
|
<div x-show="open" x-cloak class="border-t border-line px-6 py-5">
|
|
@if ($isProtected)
|
|
<x-ui.alert variant="warning" class="mb-4">{{ __('roles.owner_locked') }}</x-ui.alert>
|
|
@endif
|
|
|
|
@error('granted.'.$role->id)<p class="mb-3 text-xs text-danger">{{ $message }}</p>@enderror
|
|
|
|
<div class="grid gap-x-8 gap-y-6 sm:grid-cols-2">
|
|
@foreach ($groups as $prefix => $names)
|
|
<div>
|
|
<h3 class="text-xs font-semibold uppercase tracking-wide text-faint">{{ $prefix }}</h3>
|
|
<div class="mt-2 space-y-2">
|
|
@foreach ($names as $permission)
|
|
<label class="flex items-start gap-2 text-sm text-body">
|
|
<input type="checkbox" value="{{ $permission }}"
|
|
wire:model="granted.{{ $role->id }}"
|
|
@disabled($isProtected)
|
|
class="mt-0.5 size-4 shrink-0 rounded border-line-strong accent-[var(--accent-active)] disabled:opacity-50">
|
|
<span class="min-w-0">
|
|
<span class="block">{{ __('roles.can.'.$permission) }}</span>
|
|
<span class="block font-mono text-[11px] text-faint">{{ $permission }}</span>
|
|
</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
@unless ($isProtected)
|
|
<div class="mt-6 flex flex-wrap gap-2 border-t border-line pt-5">
|
|
<x-ui.button variant="primary" wire:click="save({{ $role->id }})"
|
|
wire:loading.attr="disabled" wire:target="save({{ $role->id }})">
|
|
{{ __('common.save') }}
|
|
</x-ui.button>
|
|
@if ($role->users_count === 0)
|
|
<x-ui.button variant="ghost" wire:click="delete({{ $role->id }})"
|
|
wire:loading.attr="disabled" wire:target="delete({{ $role->id }})">
|
|
{{ __('roles.delete') }}
|
|
</x-ui.button>
|
|
@endif
|
|
</div>
|
|
@endunless
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|