@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 ──────────────────────────────────────────────────────── --}}
{{-- ── 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') }}
| {{ __('Type') }} |
{{ __('Sessions') }} |
{{ __('Avg. duration') }} |
{{ __('Avg. pages') }} |
{{ __('Bounce rate') }} |
@foreach (['guest','free','premium','pro'] as $type)
@php $s = $sessionStats[$type] ?? null; @endphp
|
{{ $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) . '%' : '—' }}
|
@endforeach
{{-- ── Top pages ────────────────────────────────────────────────────────── --}}
{{ __('Top pages') }}
| {{ __('Page') }} |
{{ __('Total') }} |
{{ $typeLabels['guest'] }} |
{{ $typeLabels['free'] }} |
{{ $typeLabels['premium'] }} |
{{ $typeLabels['pro'] }} |
@foreach ($topPages as $row)
| {{ $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) : '–' }} |
@endforeach
@if ($topPages->isEmpty())
| {{ __('No data') }} |
@endif
{{-- ── 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
| {{ __('Source') }} |
{{ __('Sessions') }} |
% |
Prem. |
PRO |
@forelse ($sourceStats as $row)
| {{ $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) : '–' }} |
@empty
| {{ __('No data') }} |
@endforelse
{{ __('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
| {{ __('Country') }} |
{{ __('Region') }} |
{{ __('Sessions') }} |
% |
{{ $typeLabels['guest'] }} |
{{ $typeLabels['free'] }} |
{{ $typeLabels['premium'] }} |
PRO |
@foreach ($regionStats->take(25) as $row)
|
{{ $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']) : '–' }} |
@endforeach
{{ __('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)
| {{ __('Filter') }} |
{{ __('Uses') }} |
% |
{{ $typeLabels['free'] }} |
{{ $typeLabels['premium'] }} |
PRO |
@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
| {{ $filterLabels[$key] ?? $key }} |
{{ $fmt($total_uses) }} |
|
{{ $free_uses > 0 ? $fmt($free_uses) : '–' }} |
{{ $prem_uses > 0 ? $fmt($prem_uses) : '–' }} |
{{ $pro_uses > 0 ? $fmt($pro_uses) : '–' }} |
@endforeach
@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