152 lines
8.0 KiB
PHP
152 lines
8.0 KiB
PHP
@props([
|
|
// [[route, icon, translation key], …] or [['label' => …, 'items' => [...]], …]
|
|
'groups',
|
|
// dot-prefix for the labels, e.g. 'dashboard.nav' or 'admin.nav'
|
|
'prefix',
|
|
// console routes are matched through AdminArea so recovery hostnames still
|
|
// highlight; the portal uses the router directly
|
|
'console' => false,
|
|
'footer' => null,
|
|
])
|
|
@php
|
|
// Die Zahlen fuer den Block „Braucht dich". Einmal je Seitenaufruf geholt,
|
|
// nicht je Eintrag — und dahinter ein Zwischenspeicher, weil diese Leiste
|
|
// auf JEDER Konsolenseite rendert. Das Portal hat keine, dort bleibt es
|
|
// ein leeres Feld ohne Abfrage.
|
|
$counts = $console ? \App\Support\Navigation::attentionCounts() : [];
|
|
@endphp
|
|
{{--
|
|
The sidebar, shared by both shells.
|
|
|
|
Light, like everything else. The console used to be dark, which made it a
|
|
second product: two ideas of what a card is, two oranges, two status
|
|
palettes. What actually differs between an operator and a customer is
|
|
density, and that lives in the tokens — not here.
|
|
--}}
|
|
<aside
|
|
class="fixed inset-y-0 left-0 z-50 flex w-[15.5rem] flex-col border-r border-line bg-surface px-3 py-4 shadow-md transition-transform lg:static lg:h-screen lg:translate-x-0 lg:shadow-none"
|
|
:class="nav ? 'translate-x-0' : '-translate-x-full'"
|
|
>
|
|
<div class="flex shrink-0 items-center justify-between gap-2 px-2.5 pb-5">
|
|
<a href="{{ $console ? \App\Support\AdminArea::home() : route('dashboard') }}" class="flex items-center gap-2.5">
|
|
<x-ui.brand :size="$console ? 'sm' : 'md'" />
|
|
@if ($console)
|
|
<span class="rounded-sm bg-accent-subtle px-1.5 py-0.5 font-mono text-[9.5px] font-semibold uppercase tracking-[0.1em] text-accent-text">{{ __('admin.badge') }}</span>
|
|
@endif
|
|
</a>
|
|
<button type="button" class="lg:hidden -mr-1 inline-flex min-h-11 min-w-11 items-center justify-center rounded text-muted"
|
|
@click="nav = false" aria-label="{{ __('dashboard.close_nav') }}">
|
|
<x-ui.icon name="x" />
|
|
</button>
|
|
</div>
|
|
|
|
{{-- The only scroll region in the sidebar: header above and footer below
|
|
stay put (operator requirement), only the nav list itself scrolls
|
|
when it is taller than the viewport. overflow-y-auto (not -hidden)
|
|
on a flex-1 child resolves its automatic min-height to 0, which is
|
|
what lets it shrink to the available space instead of stretching the
|
|
aside past the viewport — same mechanism <main> already relies on. --}}
|
|
<div class="min-h-0 flex-1 overflow-y-auto">
|
|
@foreach ($groups as $group)
|
|
@php
|
|
// Sichtbarkeit EINMAL je Gruppe entscheiden, nicht erst je
|
|
// Eintrag. Vorher wurde die Überschrift bedingungslos gedruckt
|
|
// und stand deshalb auch über einer Gruppe, von der dieser
|
|
// Betreiber keinen einzigen Eintrag sehen darf — eine
|
|
// Überschrift mit nichts darunter liest sich wie ein
|
|
// Ladefehler, nicht wie eine Berechtigung.
|
|
//
|
|
// Bis zur Neuordnung der Konsolen-Navigation fiel das nicht
|
|
// auf, weil jede Gruppe mindestens einen ungeschützten Eintrag
|
|
// trug. „Post" tut das nicht.
|
|
//
|
|
// $capability ist ein einzelner Fähigkeitsname, ein Array von
|
|
// Namen im Sinne von „irgendeine davon" (App\Support\Navigation
|
|
// hält fest, welche Einträge das benutzen), oder null für
|
|
// „alle". Laravels eigenes can() liest ein Array als „alle
|
|
// davon" — die falsche Logik für einen Eintrag, den man mit
|
|
// der einen ODER der anderen Fähigkeit erreicht. Daher die
|
|
// ausgeschriebene Verzweigung statt eines bloßen ->can().
|
|
$visible = collect($group['items'])->filter(function (array $item) {
|
|
$capability = $item[3];
|
|
|
|
if ($capability === null) {
|
|
return true;
|
|
}
|
|
|
|
return is_array($capability)
|
|
? collect($capability)->contains(fn ($one) => auth()->user()?->can($one))
|
|
: (bool) auth()->user()?->can($capability);
|
|
});
|
|
@endphp
|
|
@continue($visible->isEmpty())
|
|
@if (! empty($group['label']))
|
|
<p class="px-3 pb-2 pt-5 lbl">{{ $group['label'] }}</p>
|
|
@endif
|
|
<nav class="space-y-0.5">
|
|
@foreach ($visible as $item)
|
|
@php
|
|
[$route, $icon, $key] = $item;
|
|
// Fuenftes Feld, optional: der Schluessel, unter dem
|
|
// Navigation::attentionCounts() die Zahl fuehrt. Als
|
|
// Index gelesen statt destrukturiert — das Portal hat
|
|
// vier Felder je Eintrag, und eine Destrukturierung auf
|
|
// fuenf warnte dort bei jedem einzelnen.
|
|
$count = isset($item[4]) ? ($counts[$item[4]] ?? 0) : 0;
|
|
@endphp
|
|
<x-ui.nav-item
|
|
:href="route($route)"
|
|
:active="\App\Support\Navigation::isCurrent($route, $console)"
|
|
@click="nav = false"
|
|
>
|
|
<x-slot:icon><x-ui.icon :name="$icon" class="size-[1.15rem]" /></x-slot:icon>
|
|
{{ __($prefix.'.'.$key) }}
|
|
{{-- Bei null KEINE Plakette, der Eintrag bleibt aber
|
|
stehen: eine Seite, die verschwindet, sobald nichts
|
|
offen ist, ist genau dann nicht erreichbar, wenn man
|
|
nachsehen will, ob wirklich nichts offen ist. --}}
|
|
@if ($count > 0)
|
|
<x-slot:badge>
|
|
<span class="rounded-pill bg-accent px-2 py-0.5 text-xs font-semibold text-on-accent">{{ $count }}</span>
|
|
</x-slot:badge>
|
|
@endif
|
|
</x-ui.nav-item>
|
|
@endforeach
|
|
</nav>
|
|
@endforeach
|
|
</div>
|
|
|
|
{{-- Die laufende Version — und, wenn es eine neuere gibt, dass es sie gibt.
|
|
Sie stand hier schon, aber als toter Text: die Seite, auf der man
|
|
etwas damit tun kann, liegt seit dem Umbau der Leiste im
|
|
Einrichtungsbereich. Der Betreiber hat genau das gemeldet — „man sieht
|
|
es nicht, ohne genau hinzuklicken".
|
|
|
|
Also bleibt die Zeile, wo sie war, und wird zum Weg dorthin. Ist etwas
|
|
verfuegbar, sagt sie es in der Akzentfarbe statt in Grau; ist nichts
|
|
verfuegbar, bleibt sie so leise wie vorher. Eine Zeile, die immer
|
|
ruft, ruft nie. --}}
|
|
@if ($footer)
|
|
@php $updateWaiting = ($counts['update'] ?? 0) > 0; @endphp
|
|
@if ($console)
|
|
<a href="{{ route('admin.settings') }}?tab=installation"
|
|
@class([
|
|
'shrink-0 rounded px-3 py-2 mt-6 block font-mono text-[11px] leading-relaxed transition-colors',
|
|
'bg-accent-subtle text-accent-text hover:bg-accent-subtle/70' => $updateWaiting,
|
|
'text-muted hover:text-ink hover:bg-surface-hover' => ! $updateWaiting,
|
|
])>
|
|
{{ $footer }}
|
|
@if ($updateWaiting)
|
|
<span class="mt-0.5 block font-sans font-semibold">{{ __('admin.nav_update_waiting') }}</span>
|
|
@endif
|
|
</a>
|
|
@else
|
|
<p class="shrink-0 px-3 pt-6 font-mono text-[11px] leading-relaxed text-muted">{{ $footer }}</p>
|
|
@endif
|
|
@endif
|
|
</aside>
|
|
|
|
{{-- Backdrop. Tapping it closes the drawer, which is the behaviour every
|
|
phone user tries first. --}}
|
|
<div x-show="nav" x-cloak @click="nav = false" class="fixed inset-0 z-40 bg-[color-mix(in_srgb,var(--plate)_42%,transparent)] lg:hidden"></div>
|