@extends('admin.layout') @section('title', __('Page analytics')) @section('content') @php $fmt = fn($n) => number_format((float) $n, 0, ',', '.'); $pct = fn($a, $b) => $b > 0 ? round($a / $b * 100) : 0; $dur = function($sec) { $sec = (int) round($sec); if ($sec < 60) return $sec . 's'; if ($sec < 3600) return floor($sec/60) . 'm ' . ($sec%60) . 's'; return floor($sec/3600) . 'u ' . floor(($sec%3600)/60) . 'm'; }; $periods = [ '24h' => __('Last 24 hours'), 'week' => __('Last week'), 'month' => __('Last month'), 'year' => __('Last year'), ]; $typeColors = [ 'guest' => '#6b7280', 'free' => '#3b82f6', 'premium' => '#f59e0b', 'pro' => '#8b5cf6', ]; $typeLabels = [ 'guest' => __('Guest'), 'free' => __('Free'), 'premium' => __('Premium'), 'pro' => 'PRO', ]; $sourceLabels = [ 'direct' => __('Direct'), 'organic' => __('Organic search'), 'social' => __('Social media'), 'paid' => __('Paid ads'), 'email' => __('E-mail'), 'pwa' => 'PWA / App', 'referral' => __('Referral'), ]; $featureLabels = [ 'dashboard' => 'Dashboard', 'matching' => __('Matching'), 'chat' => 'Chat', 'groups' => __('Groups'), 'events' => __('Events'), 'premium' => 'Premium / PRO', 'profile' => __('Profile'), ]; $filterLabels = [ 'gender' => __('Gender'), 'age_from' => __('Age (from)'), 'age_to' => __('Age (to)'), 'city' => __('City'), 'zip' => __('Zip code'), 'country' => __('Country'), 'lang_1' => __('Language 1'), 'lang_2' => __('Language 2'), 'lang_3' => __('Language 3'), 'diagnosis' => __('Diagnosis'), 'strength' => __('Strength'), ]; $sc = fn($label, $value, $color = 'gray', $sub = '') => "

{$label}

{$value}

" . ($sub ? "

{$sub}

" : '') . "
"; $days = ['Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za', 'Zo']; @endphp {{-- ── Period tabs ──────────────────────────────────────────────────────── --}}
@foreach ($periods as $key => $label) {{ $label }} @endforeach
{{-- ── Summary cards ────────────────────────────────────────────────────── --}}
{!! $sc(__('Page views'), $fmt($totalViews), 'blue') !!} {!! $sc(__('Unique sessions'), $fmt($totalSessions), 'violet') !!} {!! $sc(__('Avg. session duration'), $dur($avgDurationSec), 'green') !!} {!! $sc(__('Avg. pages / session'), number_format($avgPages, 1, ',', '.'), 'amber') !!}
{{-- ── Views by user type & Gender ────────────────────────────────────── --}}
{{-- Views by type --}}

{{ __('Page views by user type') }}

@foreach (['guest','free','premium','pro'] as $type) @php $n = $byUserType[$type] ?? 0; $p = $pct($n, max(1,$totalViews)); @endphp
{{ $typeLabels[$type] }} {{ $fmt($n) }} ({{ $p }}%)
@endforeach
{{-- Gender breakdown --}}

{{ __('Gender (logged-in users)') }}

@php $genderTotals = ['male' => 0, 'female' => 0, 'other' => 0]; foreach ($genderStats as $g) { $genderTotals[$g->gender] = ($genderTotals[$g->gender] ?? 0) + $g->sessions; } $genderTotal = array_sum($genderTotals); $genderColors = ['male' => '#3b82f6', 'female' => '#ec4899', 'other' => '#6b7280']; $genderNames = ['male' => __('Male'), 'female' => __('Female'), 'other' => __('Other')]; @endphp @foreach (['male','female','other'] as $g) @php $n = $genderTotals[$g]; $p = $pct($n, max(1,$genderTotal)); @endphp
{{ $genderNames[$g] }} {{ $fmt($n) }} ({{ $p }}%)
@endforeach
{{-- ── Session stats per user type ─────────────────────────────────────── --}}

{{ __('Session statistics by user type') }}

@foreach (['guest','free','premium','pro'] as $type) @php $s = $sessionStats[$type] ?? null; @endphp @endforeach
{{ __('Type') }} {{ __('Sessions') }} {{ __('Avg. duration') }} {{ __('Avg. pages') }} {{ __('Bounce rate') }}
{{ $typeLabels[$type] }} {{ $s ? $fmt($s->sessions) : '—' }} {{ $s ? $dur($s->avg_duration_sec) : '—' }} {{ $s ? number_format($s->avg_pages, 1, ',', '.') : '—' }} {{ $s && $s->sessions > 0 ? $pct($s->bounces, $s->sessions) . '%' : '—' }}
{{-- ── Top pages ────────────────────────────────────────────────────────── --}}

{{ __('Top pages') }}

@foreach ($topPages as $row) @endforeach @if ($topPages->isEmpty()) @endif
{{ __('Page') }} {{ __('Total') }} {{ $typeLabels['guest'] }} {{ $typeLabels['free'] }} {{ $typeLabels['premium'] }} {{ $typeLabels['pro'] }}
{{ $row->path }} {{ $fmt($row->total) }} {{ $row->guest > 0 ? $fmt($row->guest) : '–' }} {{ $row->free > 0 ? $fmt($row->free) : '–' }} {{ $row->premium > 0 ? $fmt($row->premium) : '–' }} {{ $row->pro > 0 ? $fmt($row->pro) : '–' }}
{{ __('No data') }}
{{-- ── Feature engagement ───────────────────────────────────────────────── --}}

{{ __('Feature engagement') }}

@php $featureMax = max(1, $featureEngagement->max('total')); @endphp
@foreach ($featureEngagement as $key => $data) @php $p = $pct($data['total'], $featureMax); @endphp
{{ $featureLabels[$key] ?? $key }} {{ $fmt($data['total']) }} {{ __('views') }}
@foreach (['guest','free','premium','pro'] as $type) @php $tp = $pct($data[$type], max(1,$data['total'])) * $p / 100; @endphp @if ($data[$type] > 0)
@endif @endforeach
@foreach (['guest','free','premium','pro'] as $type) @if ($data[$type] > 0) {{ $typeLabels[$type] }}: {{ $fmt($data[$type]) }} @endif @endforeach
@endforeach
{{-- ── Traffic sources ──────────────────────────────────────────────────── --}}

{{ __('Traffic sources') }}

@php $sourceTotal = $sourceStats->sum('sessions'); @endphp
@forelse ($sourceStats as $row) @empty @endforelse
{{ __('Source') }} {{ __('Sessions') }} % Prem. PRO
{{ $sourceLabels[$row->source_type] ?? $row->source_type }} {{ $fmt($row->sessions) }} {{ $pct($row->sessions, max(1,$sourceTotal)) }}% {{ $row->premium > 0 ? $fmt($row->premium) : '–' }} {{ $row->pro > 0 ? $fmt($row->pro) : '–' }}
{{ __('No data') }}

{{ __('Top landing pages per source') }}

@php $haslanding = false; @endphp @foreach ($sourceStats->take(4) as $row) @php $pages = $landingPages[$row->source_type] ?? collect(); @endphp @if ($pages->isNotEmpty()) @php $haslanding = true; @endphp

{{ $sourceLabels[$row->source_type] ?? $row->source_type }}

@foreach ($pages->take(3) as $lp)
{{ $lp->landing_page }} {{ $lp->sessions }}
@endforeach
@endif @endforeach @if (!$haslanding)

{{ __('No data') }}

@endif
{{-- ── Browser & Device ─────────────────────────────────────────────────── --}}

{{ __('Device type') }}

@php $deviceTotal = $deviceStats->sum('sessions'); $deviceColors = ['desktop' => '#3b82f6', 'mobile' => '#10b981', 'tablet' => '#f59e0b']; $deviceLabels = ['desktop' => __('Desktop'), 'mobile' => __('Mobile'), 'tablet' => __('Tablet')]; @endphp @foreach (['desktop','mobile','tablet'] as $d) @php $n = $deviceStats[$d]->sessions ?? 0; $p = $pct($n, max(1,$deviceTotal)); @endphp
{{ $deviceLabels[$d] }} {{ $fmt($n) }} ({{ $p }}%)
@endforeach

{{ __('Browser') }}

@php $browserTotal = $browserStats->sum('sessions'); @endphp
@forelse ($browserStats as $row) @php $p = $pct($row->sessions, max(1,$browserTotal)); @endphp
{{ $row->browser }}
{{ $p }}%
@empty

{{ __('No data') }}

@endforelse
{{-- ── IP regions by user category ─────────────────────────────────────── --}}

{{ __('IP regions by user category') }}

@if ($regionStats->isNotEmpty()) @php $regionTotal = $regionStats->sum('total'); $flagEmoji = function(string $code): string { $code = strtoupper($code); if (strlen($code) !== 2) return '🌐'; return mb_chr(0x1F1E6 - 0x41 + ord($code[0])) . mb_chr(0x1F1E6 - 0x41 + ord($code[1])); }; @endphp
@foreach ($regionStats->take(25) as $row) @endforeach
{{ __('Country') }} {{ __('Region') }} {{ __('Sessions') }} % {{ $typeLabels['guest'] }} {{ $typeLabels['free'] }} {{ $typeLabels['premium'] }} PRO
{{ $flagEmoji($row['code']) }}{{ $row['name'] }} {{ $row['region'] ?: '—' }} {{ $fmt($row['total']) }} {{ $pct($row['total'], max(1, $regionTotal)) }}% {{ $row['guest'] > 0 ? $fmt($row['guest']) : '–' }} {{ $row['free'] > 0 ? $fmt($row['free']) : '–' }} {{ $row['premium'] > 0 ? $fmt($row['premium']) : '–' }} {{ $row['pro'] > 0 ? $fmt($row['pro']) : '–' }}

{{ __('Country resolved from visitor IP via ip-api.com · cached 30 days per IP · private IPs excluded') }}

@else

{{ __('No geodata available — check that ip-api.com is reachable.') }}

@endif
{{-- ── Hourly heatmap ───────────────────────────────────────────────────── --}}

{{ __('Activity heatmap (day × hour)') }}

@foreach (range(0,23) as $h)
{{ $h }}
@endforeach
@foreach ($days as $di => $dayName)
{{ $dayName }}
@foreach (range(0,23) as $h) @php $val = $heatmap[$di][$h]; $intensity = $heatmapMax > 0 ? $val / $heatmapMax : 0; $opacity = round(0.08 + $intensity * 0.92, 2); $bg = $val > 0 ? "rgba(99,102,241,{$opacity})" : '#f3f4f6'; @endphp
@endforeach
@endforeach
{{ __('Low') }} @foreach ([0.1,0.3,0.5,0.7,0.9] as $op)
@endforeach {{ __('High') }}
{{-- ── Filter usage ─────────────────────────────────────────────────────── --}}

{{ __('Filter usage') }} ({{ $fmt($filterTotalSearches) }} {{ __('searches with filters') }})

@if ($filterTotalSearches > 0)
@foreach ($filterKeys as $key) @php $total_uses = (int) ($filterTotals?->$key ?? 0); $pct_uses = $pct($total_uses, max(1, $filterTotalSearches)); $free_uses = (int) ($filterByType['free']?->$key ?? 0); $prem_uses = (int) ($filterByType['premium']?->$key ?? 0); $pro_uses = (int) ($filterByType['pro']?->$key ?? 0); @endphp @endforeach
{{ __('Filter') }} {{ __('Uses') }} % {{ $typeLabels['free'] }} {{ $typeLabels['premium'] }} PRO
{{ $filterLabels[$key] ?? $key }} {{ $fmt($total_uses) }}
{{ $pct_uses }}%
{{ $free_uses > 0 ? $fmt($free_uses) : '–' }} {{ $prem_uses > 0 ? $fmt($prem_uses) : '–' }} {{ $pro_uses > 0 ? $fmt($pro_uses) : '–' }}
@else

{{ __('No filter searches recorded yet for this period.') }}

@endif
{{-- ── Conversion funnel ────────────────────────────────────────────────── --}}

{{ __('Conversion funnel') }}

@php $funnelSteps = [ ['key' => 'sessions', 'label' => __('Total sessions'), 'color' => '#6b7280'], ['key' => 'guests', 'label' => __('Guest sessions'), 'color' => '#9ca3af'], ['key' => 'registrations', 'label' => __('Registrations'), 'color' => '#3b82f6'], ['key' => 'verified', 'label' => __('E-mail verified'), 'color' => '#06b6d4'], ['key' => 'has_photo', 'label' => __('Photo uploaded'), 'color' => '#10b981'], ['key' => 'first_like', 'label' => __('First like sent'), 'color' => '#f59e0b'], ['key' => 'premium', 'label' => __('Became Premium/PRO'), 'color' => '#8b5cf6'], ]; $funnelMax = max(1, $funnel['sessions']); @endphp
@foreach ($funnelSteps as $i => $step) @php $val = $funnel[$step['key']]; $prev = $i > 0 ? $funnel[$funnelSteps[$i-1]['key']] : $val; $cvr = $i > 0 ? $pct($val, max(1,$prev)) : 100; $bar = $pct($val, $funnelMax); @endphp
{{ $step['label'] }}
{{ $bar > 10 ? $fmt($val) : '' }}
{{ $fmt($val) }} @if ($i > 0) {{ $cvr }}% @else @endif
@endforeach

{{ __('Percentages show conversion from the previous step.') }}

{{-- ── Hoe gevonden? ─────────────────────────────────────────────────────── --}}

Hoe gevonden? (registraties in periode)

@php $howFoundLabels = [ 'search_engine' => 'Zoekmachine', 'social_media' => 'Social media', 'friend_family' => 'Vriend of familie', 'professional' => 'Therapeut / coach / professional', 'news_blog' => 'Nieuwsartikel of blog', 'event' => 'Evenement of activiteit', 'other' => 'Anders', 'unknown' => 'Niet ingevuld', ]; $howFoundColors = [ 'search_engine' => '#6366f1', 'social_media' => '#ec4899', 'friend_family' => '#10b981', 'professional' => '#3b82f6', 'news_blog' => '#f59e0b', 'event' => '#f97316', 'other' => '#9ca3af', 'unknown' => '#d1d5db', ]; $howFoundTotal = max(1, $howFoundStats->sum('total')); $cumDeg = 0; $stops = []; foreach ($howFoundStats as $row) { $start = $cumDeg; $cumDeg += $row->total / $howFoundTotal * 360; $color = $howFoundColors[$row->how_found] ?? '#9ca3af'; $stops[] = "{$color} {$start}deg {$cumDeg}deg"; } $gradient = 'conic-gradient(' . implode(', ', $stops) . ')'; @endphp @if ($howFoundStats->isEmpty())

Geen registraties in deze periode.

@else
{{-- Donut --}}
{{ $howFoundStats->sum('total') }} registraties
{{-- Legend --}}
@foreach ($howFoundStats as $row) @php $label = $howFoundLabels[$row->how_found] ?? $row->how_found; $color = $howFoundColors[$row->how_found] ?? '#9ca3af'; $pctVal = round($row->total / $howFoundTotal * 100); @endphp
{{ $label }} {{ $row->total }} ({{ $pctVal }}%)
@endforeach
@endif
@endsection