Add a statistics page to the panel

The most read, reads per language with a daily chart, and published content
nobody opened, each compared with the period before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 10:19:19 +02:00
parent 8d0f81f9c5
commit 697d333ad4
9 changed files with 605 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
<x-filament-panels::page>
<style>
.st-periods { display: flex; gap: .5rem; flex-wrap: wrap; }
.st-table { width: 100%; border-collapse: collapse; font-size: .875rem; }
.st-table th { text-align: left; font-weight: 600; font-size: .75rem; text-transform: uppercase; opacity: .7; padding: .5rem; }
.st-table td { padding: .5rem; border-top: 1px solid color-mix(in oklab, currentColor 10%, transparent); }
.st-table td.num, .st-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.st-up { color: #16a34a; } .st-down { color: #dc2626; }
.st-locales { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: 1rem; }
.st-locale strong { display: block; font-size: 1.5rem; font-variant-numeric: tabular-nums; }
.st-bar { height: .375rem; border-radius: 999px; background: color-mix(in oklab, currentColor 10%, transparent); overflow: hidden; margin-top: .5rem; }
.st-bar span { display: block; height: 100%; background: var(--primary-500, #f59e0b); }
.st-unread { columns: 2 18rem; column-gap: 2rem; font-size: .875rem; }
.st-unread li { break-inside: avoid; padding: .25rem 0; }
</style>
@php($stats = $this->stats())
<div class="st-periods" role="group" aria-label="{{ __('panel.statistics.period') }}">
@foreach (\App\Filament\Pages\Statistics::PERIODS as $period)
<x-filament::button
:color="$days === $period ? 'primary' : 'gray'"
wire:click="$set('days', {{ $period }})"
:aria-pressed="$days === $period ? 'true' : 'false'"
>
{{ __('panel.statistics.last_days', ['days' => $period]) }}
</x-filament::button>
@endforeach
</div>
<x-filament::section :heading="__('panel.statistics.languages')" :description="__('panel.statistics.compared')">
<div class="st-locales">
@foreach ($stats->byLocale() as $locale => $row)
@php($delta = $this->change($row['views'], $row['previous']))
<div class="st-locale">
<span>{{ config("locales.supported.{$locale}.native", $locale) }}</span>
<strong>{{ number_format($row['views'], 0, ',', '.') }}</strong>
<span @class(['st-up' => str_starts_with($delta, '+'), 'st-down' => str_starts_with($delta, '−')])>{{ $delta }}</span>
· {{ $row['share'] }} %
<div class="st-bar" aria-hidden="true"><span style="width: {{ $row['share'] }}%"></span></div>
</div>
@endforeach
</div>
</x-filament::section>
@livewire(\App\Filament\Charts\ViewsByLanguageChart::class, ['days' => $days], key('views-chart-'.$days))
<x-filament::section :heading="__('panel.statistics.top')">
@php($top = $stats->top())
@if ($top->isEmpty())
<p>{{ __('panel.statistics.no_views') }}</p>
@else
<table class="st-table">
<thead>
<tr>
<th scope="col">{{ __('panel.statistics.columns.title') }}</th>
<th scope="col">{{ __('panel.statistics.columns.type') }}</th>
<th scope="col" class="num">{{ __('panel.statistics.columns.views') }}</th>
<th scope="col" class="num">{{ __('panel.statistics.columns.change') }}</th>
</tr>
</thead>
<tbody>
@foreach ($top as $row)
@php($delta = $this->change($row['views'], $row['previous']))
<tr>
<td>
@if ($row['url'])
<x-filament::link :href="$row['url']">{{ $row['title'] }}</x-filament::link>
@else
{{ $row['title'] }}
@endif
</td>
<td>{{ __('panel.statistics.types.'.$row['type']) }}</td>
<td class="num">{{ number_format($row['views'], 0, ',', '.') }}</td>
<td @class(['num', 'st-up' => str_starts_with($delta, '+'), 'st-down' => str_starts_with($delta, '−')])>{{ $delta }}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</x-filament::section>
<x-filament::section :heading="__('panel.statistics.unread')" :description="__('panel.statistics.unread_hint')" collapsible>
@php($unread = $stats->unread())
@if ($unread->isEmpty())
<p>{{ __('panel.statistics.all_read') }}</p>
@else
<ul class="st-unread">
@foreach ($unread as $row)
<li>
<x-filament::badge color="gray" size="sm" style="display: inline-flex">{{ __('panel.statistics.types.'.$row['type']) }}</x-filament::badge>
@if ($row['url'])
<x-filament::link :href="$row['url']">{{ $row['title'] }}</x-filament::link>
@else
{{ $row['title'] }}
@endif
</li>
@endforeach
</ul>
@endif
</x-filament::section>
</x-filament-panels::page>