Add join token analytics dashboard and align Filament views
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-04 18:21:59 +01:00
parent 48b1cfde09
commit 15e19d4e8b
17 changed files with 1176 additions and 310 deletions

View File

@@ -1,70 +1,81 @@
<div class="space-y-5">
<div class="rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800 dark:border-amber-400/60 dark:bg-amber-500/10 dark:text-amber-100">
<div class="flex flex-col gap-1">
<div class="text-xs font-semibold uppercase tracking-wide text-amber-600 dark:text-amber-300">{{ __('admin.events.join_link.event_label') }}</div>
<div class="text-base font-semibold text-amber-900 dark:text-amber-100">{{ $event->name }}</div>
</div>
<p class="mt-3 text-xs leading-relaxed text-amber-700 dark:text-amber-200">
@php
use Filament\Support\Enums\GridDirection;
use Filament\Support\Icons\Heroicon;
use Illuminate\View\ComponentAttributeBag;
$stacked = (new ComponentAttributeBag())->grid(['default' => 1], GridDirection::Column);
$tokensGrid = (new ComponentAttributeBag())->grid(['default' => 1], GridDirection::Column);
$linkGrid = (new ComponentAttributeBag())->grid(['default' => 1, 'md' => 2]);
$metricsGrid = (new ComponentAttributeBag())->grid(['default' => 1, 'sm' => 2, 'xl' => 4]);
$layoutsGrid = (new ComponentAttributeBag())->grid(['default' => 1, 'md' => 2]);
@endphp
<div {{ $stacked }}>
<x-filament::section
:heading="__('admin.events.join_link.event_label')"
:description="$event->name"
:icon="Heroicon::InformationCircle"
>
<x-slot name="afterHeader">
<x-filament::button
tag="a"
href="{{ url('/event-admin/events/' . $event->slug) }}"
target="_blank"
rel="noreferrer"
color="warning"
size="sm"
:icon="Heroicon::ArrowTopRightOnSquare"
>
{{ __('admin.events.join_link.open_admin') }}
</x-filament::button>
</x-slot>
<x-filament::badge color="warning" :icon="Heroicon::ExclamationTriangle">
{{ __('admin.events.join_link.deprecated_notice', ['slug' => $event->slug]) }}
</p>
<a
href="{{ url('/event-admin/events/' . $event->slug) }}"
target="_blank"
rel="noreferrer"
class="mt-3 inline-flex items-center gap-2 rounded bg-amber-600 px-3 py-2 text-xs font-semibold uppercase tracking-wide text-white transition hover:bg-amber-700 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 dark:hover:bg-amber-500"
>
{{ __('admin.events.join_link.open_admin') }}
</a>
</div>
</x-filament::badge>
</x-filament::section>
@if ($tokens->isEmpty())
<div class="rounded border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800 dark:border-amber-400/60 dark:bg-amber-500/10 dark:text-amber-100">
{{ __('admin.events.join_link.no_tokens') }}
</div>
<x-filament::empty-state
:heading="__('admin.events.join_link.no_tokens')"
:icon="Heroicon::Key"
/>
@else
<div class="space-y-4">
<div {{ $tokensGrid }}>
@foreach ($tokens as $token)
<div class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-900/80">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-800 dark:text-slate-100">
{{ $token['label'] ?? __('admin.events.join_link.token_default', ['id' => $token['id']]) }}
</div>
<div class="text-xs text-slate-500 dark:text-slate-400">
{{ __('admin.events.join_link.token_usage', [
'usage' => $token['usage_count'],
'limit' => $token['usage_limit'] ?? '∞',
]) }}
</div>
</div>
<div>
@if ($token['is_active'])
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-medium text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-200">
{{ __('admin.events.join_link.token_active') }}
</span>
@else
<span class="rounded-full bg-slate-200 px-3 py-1 text-xs font-medium text-slate-700 dark:bg-slate-700 dark:text-slate-200">
{{ __('admin.events.join_link.token_inactive') }}
</span>
@endif
</div>
</div>
<x-filament::card
:heading="$token['label'] ?? __('admin.events.join_link.token_default', ['id' => $token['id']])"
:description="__('admin.events.join_link.token_usage', [
'usage' => $token['usage_count'],
'limit' => $token['usage_limit'] ?? '∞',
])"
>
<x-slot name="afterHeader">
@if ($token['is_active'])
<x-filament::badge color="success" :icon="Heroicon::CheckCircle">
{{ __('admin.events.join_link.token_active') }}
</x-filament::badge>
@else
<x-filament::badge color="gray" :icon="Heroicon::PauseCircle">
{{ __('admin.events.join_link.token_inactive') }}
</x-filament::badge>
@endif
</x-slot>
<div class="mt-3 space-y-2">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
{{ __('admin.events.join_link.link_label') }}
</div>
<div class="flex flex-wrap items-center gap-3">
<code class="rounded bg-slate-100 px-2 py-1 text-xs text-slate-700 dark:bg-slate-800 dark:text-slate-100">
{{ $token['url'] }}
</code>
<button
<div {{ $stacked }}>
<x-filament::badge color="gray" :icon="Heroicon::Link">
{{ __('admin.events.join_link.link_label') }}: {{ $token['url'] }}
</x-filament::badge>
<div {{ $linkGrid }}>
<x-filament::button
x-data
@click.prevent="navigator.clipboard.writeText('{{ $token['url'] }}')"
class="rounded border border-slate-200 px-2 py-1 text-xs font-medium text-slate-600 transition hover:bg-slate-100 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800"
color="gray"
size="xs"
:icon="Heroicon::ClipboardDocument"
>
{{ __('admin.events.join_link.copy_link') }}
</button>
</x-filament::button>
</div>
</div>
@@ -73,90 +84,88 @@
@endphp
@if (!empty($analytics))
<div class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
<div class="rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-xs text-emerald-800 dark:border-emerald-500/40 dark:bg-emerald-500/10 dark:text-emerald-100">
<div class="text-[11px] uppercase tracking-wide">{{ __('admin.events.analytics.success_total') }}</div>
<div class="mt-1 text-lg font-semibold">
<div {{ $metricsGrid }}>
<x-filament::card compact>
<x-filament::badge color="success" :icon="Heroicon::CheckCircle">
{{ __('admin.events.analytics.success_total') }}:
{{ number_format($analytics['success_total'] ?? 0) }}
</div>
</div>
<div class="rounded-lg border border-rose-200 bg-rose-50 p-3 text-xs text-rose-800 dark:border-rose-500/40 dark:bg-rose-500/10 dark:text-rose-100">
<div class="text-[11px] uppercase tracking-wide">{{ __('admin.events.analytics.failure_total') }}</div>
<div class="mt-1 text-lg font-semibold">
</x-filament::badge>
</x-filament::card>
<x-filament::card compact>
<x-filament::badge color="danger" :icon="Heroicon::XCircle">
{{ __('admin.events.analytics.failure_total') }}:
{{ number_format($analytics['failure_total'] ?? 0) }}
</div>
</div>
<div class="rounded-lg border border-amber-200 bg-amber-50 p-3 text-xs text-amber-800 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-100">
<div class="text-[11px] uppercase tracking-wide">{{ __('admin.events.analytics.rate_limited_total') }}</div>
<div class="mt-1 text-lg font-semibold">
</x-filament::badge>
</x-filament::card>
<x-filament::card compact>
<x-filament::badge color="warning" :icon="Heroicon::ExclamationTriangle">
{{ __('admin.events.analytics.rate_limited_total') }}:
{{ number_format($analytics['rate_limited_total'] ?? 0) }}
</x-filament::badge>
</x-filament::card>
<x-filament::card compact>
<div {{ $stacked }}>
<x-filament::badge color="gray" :icon="Heroicon::Clock">
{{ __('admin.events.analytics.recent_24h') }}:
{{ number_format($analytics['recent_24h'] ?? 0) }}
</x-filament::badge>
@if (!empty($analytics['last_seen_at']))
<x-filament::badge color="gray" :icon="Heroicon::Eye">
{{ __('admin.events.analytics.last_seen_at', ['date' => \Carbon\Carbon::parse($analytics['last_seen_at'])->isoFormat('LLL')]) }}
</x-filament::badge>
@endif
</div>
</div>
<div class="rounded-lg border border-slate-200 bg-slate-50 p-3 text-xs text-slate-700 dark:border-slate-700 dark:bg-slate-800/70 dark:text-slate-200">
<div class="text-[11px] uppercase tracking-wide">{{ __('admin.events.analytics.recent_24h') }}</div>
<div class="mt-1 text-lg font-semibold">
{{ number_format($analytics['recent_24h'] ?? 0) }}
</div>
@if (!empty($analytics['last_seen_at']))
<div class="mt-1 text-[11px] text-slate-500 dark:text-slate-400">
{{ __('admin.events.analytics.last_seen_at', ['date' => \Carbon\Carbon::parse($analytics['last_seen_at'])->isoFormat('LLL')]) }}
</div>
@endif
</div>
</x-filament::card>
</div>
@endif
@if (!empty($token['layouts']))
<div class="mt-4 space-y-3">
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
{{ __('admin.events.join_link.layouts_heading') }}
</div>
<div class="grid gap-3 md:grid-cols-2">
<x-filament::section :heading="__('admin.events.join_link.layouts_heading')" :icon="Heroicon::QrCode">
<div {{ $layoutsGrid }}>
@foreach ($token['layouts'] as $layout)
<div class="rounded-lg border border-slate-200 bg-slate-50 p-3 text-xs text-slate-700 dark:border-slate-700 dark:bg-slate-800/70 dark:text-slate-200">
<div class="font-semibold text-slate-900 dark:text-slate-100">
{{ $layout['name'] }}
</div>
@if (!empty($layout['subtitle']))
<div class="text-[11px] text-slate-500 dark:text-slate-400">
{{ $layout['subtitle'] }}
</div>
@endif
<div class="mt-2 flex flex-wrap gap-2">
<x-filament::card
:heading="$layout['name']"
:description="$layout['subtitle'] ?? null"
>
<div {{ $stacked }}>
@foreach ($layout['download_urls'] as $format => $href)
<a
<x-filament::button
tag="a"
href="{{ $href }}"
target="_blank"
rel="noreferrer"
class="inline-flex items-center gap-1 rounded border border-amber-300 bg-amber-100 px-2 py-1 text-[11px] font-medium text-amber-800 transition hover:bg-amber-200 dark:border-amber-500/50 dark:bg-amber-500/10 dark:text-amber-200 dark:hover:bg-amber-500/20"
color="warning"
size="xs"
:icon="Heroicon::ArrowDownTray"
>
{{ strtoupper($format) }}
</a>
</x-filament::button>
@endforeach
</div>
</div>
</x-filament::card>
@endforeach
</div>
</div>
</x-filament::section>
@elseif(!empty($token['layouts_url']))
<div class="mt-4">
<a
href="{{ $token['layouts_url'] }}"
target="_blank"
rel="noreferrer"
class="inline-flex items-center gap-1 text-xs font-medium text-amber-700 underline decoration-dotted hover:text-amber-800 dark:text-amber-300"
>
{{ __('admin.events.join_link.layouts_fallback') }}
</a>
</div>
<x-filament::button
tag="a"
href="{{ $token['layouts_url'] }}"
target="_blank"
rel="noreferrer"
color="warning"
size="xs"
:icon="Heroicon::QrCode"
>
{{ __('admin.events.join_link.layouts_fallback') }}
</x-filament::button>
@endif
@if ($token['expires_at'])
<div class="mt-4 text-xs text-slate-500 dark:text-slate-400">
<x-filament::badge color="gray" :icon="Heroicon::Clock">
{{ __('admin.events.join_link.token_expiry', ['date' => \Carbon\Carbon::parse($token['expires_at'])->isoFormat('LLL')]) }}
</div>
</x-filament::badge>
@endif
</div>
</x-filament::card>
@endforeach
</div>
@endif