CluPilotCloud/resources/views/livewire/admin/roles.blade.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>