resolveFilters(); return $table ->query(fn (): Builder => $this->buildQuery($filters)) ->columns([ Tables\Columns\TextColumn::make('token_preview') ->label(__('admin.join_token_analytics.table.token')) ->copyable() ->copyMessage(__('admin.events.messages.join_link_copied')), Tables\Columns\TextColumn::make('event_label') ->label(__('admin.join_token_analytics.table.event')) ->getStateUsing(fn (EventJoinToken $record) => $this->formatEventLabel($record->event)) ->url(fn (EventJoinToken $record) => $record->event ? EventResource::getUrl('view', ['record' => $record->event]) : null) ->openUrlInNewTab(), Tables\Columns\TextColumn::make('tenant_label') ->label(__('admin.join_token_analytics.table.tenant')) ->getStateUsing(fn (EventJoinToken $record) => $record->event?->tenant?->name ?? __('admin.common.unnamed')), Tables\Columns\TextColumn::make('success_total') ->label(__('admin.join_token_analytics.table.success')) ->numeric(), Tables\Columns\TextColumn::make('failure_total') ->label(__('admin.join_token_analytics.table.failures')) ->numeric(), Tables\Columns\TextColumn::make('rate_limited_total') ->label(__('admin.join_token_analytics.table.rate_limited')) ->numeric(), Tables\Columns\TextColumn::make('upload_total') ->label(__('admin.join_token_analytics.table.uploads')) ->numeric(), Tables\Columns\TextColumn::make('last_seen_at') ->label(__('admin.join_token_analytics.table.last_seen')) ->since() ->placeholder('—'), ]) ->paginated(false); } private function buildQuery(array $filters): Builder { $query = EventJoinToken::query() ->with(['event.tenant']) ->when($filters['event_id'], fn (Builder $builder, int $eventId) => $builder->where('event_id', $eventId)) ->withCount([ 'analytics as success_total' => function (Builder $builder) use ($filters) { $this->applyAnalyticsFilters($builder, $filters) ->whereIn('event_type', self::SUCCESS_EVENTS); }, 'analytics as failure_total' => function (Builder $builder) use ($filters) { $this->applyAnalyticsFilters($builder, $filters) ->whereIn('event_type', self::FAILURE_EVENTS); }, 'analytics as rate_limited_total' => function (Builder $builder) use ($filters) { $this->applyAnalyticsFilters($builder, $filters) ->whereIn('event_type', self::RATE_LIMIT_EVENTS); }, 'analytics as upload_total' => function (Builder $builder) use ($filters) { $this->applyAnalyticsFilters($builder, $filters) ->whereIn('event_type', self::UPLOAD_EVENTS); }, ]) ->withMax([ 'analytics as last_seen_at' => function (Builder $builder) use ($filters) { $this->applyAnalyticsFilters($builder, $filters); }, ], 'occurred_at') ->orderByDesc('failure_total') ->orderByDesc('rate_limited_total') ->orderByDesc('success_total') ->limit(10); return $query; } private function applyAnalyticsFilters(Builder $query, array $filters): Builder { return $query->whereBetween('occurred_at', [$filters['start'], $filters['end']]); } private function resolveFilters(): array { $eventId = $this->pageFilters['event_id'] ?? null; $eventId = is_numeric($eventId) ? (int) $eventId : null; $range = is_string($this->pageFilters['range'] ?? null) ? $this->pageFilters['range'] : '24h'; [$start, $end] = $this->resolveWindow($range, $eventId); return [ 'event_id' => $eventId, 'range' => $range, 'start' => $start, 'end' => $end, ]; } private function resolveWindow(string $range, ?int $eventId): array { $now = now(); $start = match ($range) { '2h' => $now->copy()->subHours(2), '6h' => $now->copy()->subHours(6), '12h' => $now->copy()->subHours(12), '7d' => $now->copy()->subDays(7), default => $now->copy()->subHours(24), }; $end = $now; if ($range === 'event_day' && $eventId) { $eventDate = Event::query()->whereKey($eventId)->value('date'); if ($eventDate) { $eventDay = Carbon::parse($eventDate); $start = $eventDay->copy()->startOfDay(); $end = $eventDay->copy()->endOfDay(); } } return [$start, $end]; } private function formatEventLabel(?Event $event): string { if (! $event) { return __('admin.common.unnamed'); } $locale = app()->getLocale(); $name = $event->name[$locale] ?? $event->name['de'] ?? $event->name['en'] ?? $event->slug ?? __('admin.common.unnamed'); $tenant = $event->tenant?->name ?? __('admin.common.unnamed'); $date = $event->date?->format('Y-m-d'); return $date ? "{$name} ({$tenant}) {$date}" : "{$name} ({$tenant})"; } private const SUCCESS_EVENTS = [ 'access_granted', 'gallery_access_granted', ]; private const RATE_LIMIT_EVENTS = [ 'token_rate_limited', 'access_rate_limited', 'download_rate_limited', ]; private const FAILURE_EVENTS = [ 'invalid_token', 'token_expired', 'token_revoked', 'event_not_public', 'gallery_expired', 'token_rate_limited', 'access_rate_limited', 'download_rate_limited', ]; private const UPLOAD_EVENTS = [ 'upload_completed', ]; }