@extends('layouts.admin') @section('title', 'Licenses') @section('heading', 'Licenses') @section('subheading', 'Manage and monitor all software licenses.') @php $canManage = auth()->user()?->isOwner() ?? false; $expiringDays = (int) config('licensing.expiring_soon_days', 30); // The controller supports exactly two server-side filters. Everything the // toolbar offers beyond these refines the rows already on screen, and the // footer says so whenever it is doing that. $activeStatus = request('status'); $search = request('q'); $isFiltered = filled($activeStatus) || filled($search); // Package options come from the rows on this page, not a fresh query: the // filter is client-side, so offering packages that cannot match anything // visible would just be a list of dead ends. $packageNames = $licenses->pluck('package.name')->filter()->unique()->sort()->values(); // Quick filters that the server can actually honour. Each one is a link, so // it covers the whole database and survives a bookmark. $chips = [ ['label' => 'All licenses', 'params' => [], 'icon' => 'layers'], ['label' => 'Active', 'params' => ['status' => 'active'], 'icon' => 'check-circle'], ['label' => 'Expiring soon', 'params' => ['status' => 'active', 'refine' => 'expiring'], 'icon' => 'clock'], ['label' => 'Expired', 'params' => ['status' => 'expired'], 'icon' => 'x-circle'], ['label' => 'Suspended', 'params' => ['status' => 'suspended'], 'icon' => 'pause'], ]; $currentChip = fn (array $params) => ($params['status'] ?? null) === ($activeStatus ?: null) && ($params['refine'] ?? null) === (request('refine') ?: null); @endphp @section('header-actions') @if ($canManage) New @endif @endsection @section('content') {{-- lg:h-full makes the page exactly the viewport, so the table takes the space the cards and toolbar leave and scrolls inside itself instead of trailing off below the fold. Below lg the page scrolls normally. --}}
{{-- ── Statistics ──────────────────────────────────────────────── --}} {{-- ── Toolbar ─────────────────────────────────────────────────── --}}
{{-- Server filter: reloads, covers every licence in the database. --}} @if ($isFiltered) Reset @endif {{-- View controls. These carry no `name`, so submitting the form never turns a view preference into a query string. --}}
@if ($packageNames->count() > 1) @endif Export this view Export selected
{{-- ── Quick filters ───────────────────────────────────────── --}}
@foreach ($chips as $chip) $currentChip($chip['params'])])> {{ $chip['label'] }} @endforeach {{-- The one quick filter the server cannot express. It toggles instead of navigating, which is also the honest signal that it works on a different scope from the chips beside it. --}}
{{-- ── Bulk actions ────────────────────────────────────────────── --}} @if ($canManage)
selected
{{-- Bulk work is one request per licence, and each one pushes to a live installation. Showing the count moving is the difference between "working" and "frozen". --}}
Applying… of
@endif {{-- ── Grid ────────────────────────────────────────────────────── --}}
{{-- Skeleton. Covers the grid the moment a navigation starts, so a filter change never leaves the old rows sitting there looking like the new result. --}}
@for ($i = 0; $i < 8; $i++)
@endfor
@if ($licenses->isEmpty())
@if ($isFiltered) Reset filters @endif @if ($canManage) Generate License @endif
@else
@foreach ($licenses as $license) @include('licenses.partials.row', ['license' => $license, 'expiringDays' => $expiringDays]) @endforeach
{{-- indeterminate exists only as a DOM property, so it has to be assigned rather than bound as an attribute. --}} License Key Customer Domain Package Status Created Expires Actions
{{-- Every row on this page was filtered out client-side. The server still has results, so this is not the empty state. --}}

Nothing on this page matches

The refinement hid all rows here. Clear it, or search instead — search covers every license.

{{-- ── Footer ──────────────────────────────────────────── --}}

Showing {{ number_format($licenses->firstItem()) }}–{{ number_format($licenses->lastItem()) }} of {{ number_format($licenses->total()) }} {{ Str::plural('license', $licenses->total()) }} {{-- Said out loud rather than left to be discovered: the refinement is not a search of the whole database. --}} · of shown on this page

! $licenses->previousPageUrl()]) aria-label="Previous page"> @php // A window around the current page. A hundred numbered // links is not navigation. $last = $licenses->lastPage(); $current = $licenses->currentPage(); $from = max(1, min($current - 2, $last - 4)); $to = min($last, max($current + 2, 5)); @endphp @if ($from > 1) 1 @if ($from > 2)…@endif @endif @for ($page = $from; $page <= $to; $page++) $page === $current, ]) @if ($page === $current) aria-current="page" @endif>{{ $page }} @endfor @if ($to < $last) @if ($to < $last - 1)…@endif {{ $last }} @endif ! $licenses->nextPageUrl()]) aria-label="Next page">
@endif
{{-- ── Toast ───────────────────────────────────────────────────── --}}
@endsection