@extends('admin.layout', ['menuKey' => 'analytics'])
@section('title', 'Analytics')
@php
use App\Support\Charts;
$range = $d['range'];
$pct = fn ($a, $b) => $b > 0 ? number_format($a / $b * 100, 1).'%' : '–';
$rowsTable = fn (string $h, array $rows, string $n = 'Count') => ['head' => [$h, $n], 'rows' => array_map(fn ($r) => [$r['label'], number_format($r['n'])], $rows)];
@endphp
@section('content')
Overview
Analytics
The whole site, counted on our own server: people (not bots, not admins), where they came from, what they looked for, and what it earned. Updated every 10 minutes. Google Analytics still runs alongside for anyone who wants its reports.
Site health
@include('charts.range', ['range' => $range, 'csv' => request()->fullUrlWithQuery(['format' => 'csv'])])
@include('charts.kpis', ['tiles' => $d['kpis']])
@include('dash._trend', ['series' => $d['series'], 'range' => $range, 'title' => 'Over time'])
Visitors
@include('charts.bars', ['rows' => $d['sources']])
@include('dash._table', ['table' => $rowsTable('Source', $d['sources'], 'Page views')])
@include('charts.bars', ['rows' => $d['referrers'], 'empty' => 'No other sites have sent visitors in this period.'])
@include('charts.usmap', ['values' => $d['states'], 'noun' => 'page views'])
Countries
@include('charts.bars', ['rows' => $d['countries']])
Cities
@include('charts.bars', ['rows' => $d['cities'], 'empty' => 'No cities yet (needs the MaxMind database).'])
@include('dash._table', ['table' => ['head' => ['State', 'Page views'], 'rows' => collect($d['states'])->sortDesc()->map(fn ($n, $s) => [Charts::stateName($s), number_format($n)])->values()->all()]])
@include('charts.columns', ['values' => $d['hours'], 'noun' => 'page views'])
@include('dash._table', ['table' => ['head' => ['Hour', 'Page views'], 'rows' => array_map(fn ($k, $v) => [Charts::hour($k), number_format($v)], array_keys($d['hours']), $d['hours'])]])
@include('charts.bars', ['rows' => $d['devices']])
Browsers
@include('charts.bars', ['rows' => $d['browsers']])
@include('charts.bars', ['rows' => $d['pages']])
Jobs and search
From search to reply
All posts together
@php $top = $d['funnel'][0][1] ?: 1; @endphp
@foreach ($d['funnel'] as $i => [$label, $n])
{{ $label }}{{ number_format($n) }}@if ($i === 1){{ $pct($n, $d['funnel'][0][1]) }} of those seen@endif
@endforeach
@include('charts.bars', ['rows' => $d['searches'], 'empty' => 'No searches by words yet.'])
Places searched
@include('charts.bars', ['rows' => $d['searchPlaces'], 'empty' => 'No place searches yet.'])
@include('charts.bars', ['rows' => $d['noResults'], 'empty' => 'Every search found something.'])
Posting
New posts, by how they were made and paid for
@include('charts.bars', ['rows' => $d['posting']['source'], 'empty' => 'No new posts in this period.'])
Paid with
@include('charts.bars', ['rows' => $d['posting']['paid'], 'empty' => 'No new posts in this period.'])
@include('charts.bars', ['rows' => $d['posting']['limits'], 'empty' => 'Nobody hit a limit.'])
By email domain
@include('charts.bars', ['rows' => $d['posting']['limitWho'], 'empty' => 'Nobody hit a limit.'])
Job packs
@include('charts.kpis', ['tiles' => [
['label' => 'Packs sold', 'value' => number_format($d['packs']['sold']), 'sub' => number_format($d['packs']['postsSold']).' posts'],
['label' => 'Pack revenue', 'value' => '$'.number_format($d['packs']['revenue'] / 100, 2), 'sub' => 'after refunds, before tax'],
['label' => 'Pack posts used', 'value' => number_format($d['packs']['used']), 'sub' => number_format($d['packs']['returned']).' given back'],
['label' => 'Given free', 'value' => number_format($d['packs']['postsGiven']), 'sub' => 'posts, by admins'],
]])
Packs sold, by size
@include('charts.bars', ['rows' => $d['packs']['bySize'], 'empty' => 'No packs sold in this period.'])
@include('charts.bars', ['rows' => $d['packs']['top'], 'empty' => 'No unused pack posts.'])
Replies to candidates (No Ghosting)
@include('charts.kpis', ['tiles' => [
['label' => 'Answered', 'value' => $d['replies']['rate'] === null ? '–' : $d['replies']['rate'].'%', 'sub' => 'of '.number_format($d['replies']['conversations']).' conversations'],
['label' => 'Usual reply', 'value' => $d['replies']['median'] ? $d['replies']['median'].' bd' : '–', 'sub' => 'business days (median)'],
['label' => 'Waiting now', 'value' => number_format($d['replies']['waiting']), 'sub' => 'past '.\App\Services\Orgs\ReplyStats::PENDING_DAYS.' business days'],
['label' => 'Reminders', 'value' => number_format($d['replies']['nudges']), 'sub' => number_format($d['replies']['closedNotices']).' "job closed" notes to candidates'],
]])
No Ghosting badge: {{ $d['replies']['lost'] }} verified {{ $d['replies']['lost'] === 1 ? 'organization has' : 'organizations have' }} lost it, {{ $d['replies']['warned'] }} warned.
@include('charts.bars', ['rows' => $d['replies']['waitingBy'], 'empty' => 'Nobody is waiting. Every candidate has an answer.'])
Filters people use on the job board
@include('charts.bars', ['rows' => $d['filters'], 'empty' => 'No filters used in this period.'])
Blog shares
@include('charts.bars', ['rows' => $d['shares']['by'], 'empty' => 'No articles shared in this period.'])
@include('charts.bars', ['rows' => $d['shares']['top'], 'empty' => 'No articles shared in this period.'])
Bulk uploads
@include('charts.kpis', ['tiles' => [
['label' => 'Rows uploaded', 'value' => number_format($d['bulk']['rows']), 'sub' => number_format($d['bulk']['valid']).' passed the checks'],
['label' => 'Jobs posted', 'value' => number_format($d['bulk']['posted']), 'sub' => number_format($d['bulk']['live']).' went live at once'],
['label' => 'Not posted', 'value' => number_format($d['bulk']['notPosted']), 'sub' => 'refused when publishing'],
['label' => 'Not finished', 'value' => number_format($d['bulk']['abandoned']), 'sub' => 'uploads never posted'],
]])
Uploads by outcome
@include('charts.bars', ['rows' => $d['bulk']['status'], 'empty' => 'No bulk uploads in this period.'])
@include('charts.bars', ['rows' => $d['bulk']['fields'], 'empty' => 'No problems found in uploaded rows.'])
Sponsored placements
@include('charts.kpis', ['tiles' => [
['label' => 'Views', 'value' => Charts::compact($d['sponsor']['views'])],
['label' => 'Clicks', 'value' => Charts::compact($d['sponsor']['clicks']), 'sub' => $pct($d['sponsor']['clicks'], $d['sponsor']['views']).' click rate'],
['label' => 'Spend', 'value' => Charts::money($d['sponsor']['spend'])],
]])
By placement
@include('charts.bars', ['rows' => array_map(fn ($r) => $r + ['note' => number_format($r['clicks']).' clicks · '.$pct($r['clicks'], $r['n'])], $d['sponsor']['placements'])])
Top campaigns
By spend, then views
@include('charts.bars', ['rows' => array_map(fn ($r) => $r + ['note' => number_format($r['clicks']).' clicks · '.($r['house'] ? 'house' : Charts::money($r['spend']))], $d['sponsor']['top']), 'empty' => 'No sponsored views in this period.'])
Email and speed
@include('charts.kpis', ['tiles' => [
['label' => 'Job alert emails', 'value' => Charts::compact($d['email']['alerts']), 'sub' => 'jobs sent to candidates'],
['label' => 'Bounces', 'value' => number_format($d['email']['bounces']), 'delta' => Charts::delta($d['email']['bounces'], $d['email']['prevBounces'], 'down', $range->previous()->label)],
['label' => 'Spam complaints', 'value' => number_format($d['email']['complaints'])],
]])
@include('charts.kpis', ['tiles' => [
['label' => 'Typical', 'value' => $d['ms']['p50'] === null ? '–' : $d['ms']['p50'].' ms'],
['label' => '95% of pages under', 'value' => $d['ms']['p95'] === null ? '–' : $d['ms']['p95'].' ms'],
]])
Slowest page types (average ms)
@include('charts.bars', ['rows' => $d['ms']['slow'], 'empty' => 'Not enough pages yet.'])
@if ($d['since'])Counting started {{ $d['since'] }}. Revenue, posts, replies and sponsored figures come from their own records and go back further.
@endif
@endsection