@if (session('status') || $errors->any()) @push('scripts') @endpush @endif @php $statusKey = strtolower((string) $campaign->status); $statusBadge = match ($statusKey) { 'completed' => 'bg-ink-900 text-paper-0', 'running' => 'bg-wa-green/15 text-wa-deep border border-wa-green/30', 'scheduled' => 'bg-accent-amber/15 text-ink-800 border border-accent-amber/30', 'failed' => 'bg-accent-coral/15 text-accent-coral border border-accent-coral/30', 'cancelled' => 'bg-paper-100 text-ink-600 border border-paper-200', 'paused' => 'bg-paper-100 text-ink-700 border border-paper-200', default => 'bg-paper-50 text-ink-700 border border-paper-200', }; @endphp
WA Campaigns / Analytics / #{{ $campaign->id }}
{{ $campaign->campaign_name }}
{{ ucfirst($statusKey ?: 'draft') }} @if (in_array($statusKey, ['scheduled', 'paused', 'draft'], true))
@csrf
@endif @if ($statusKey === 'running')
@csrf
@endif @if (in_array($statusKey, ['paused', 'cancelled'], true))
@csrf
@endif {{-- Edit ⇄ Resend, status-gated + mutually exclusive. Edit only for draft / scheduled / paused; Resend only for completed / failed / cancelled. Running shows neither — it's in flight. --}} @if (in_array($statusKey, ['draft', 'scheduled', 'paused'], true)) Edit @elseif (in_array($statusKey, ['completed', 'failed', 'cancelled'], true))
@csrf
@endif
@csrf @method('DELETE')
{{ ucfirst((string) $campaign->campaign_type) }} {{ __('campaign') }} {{ $campaign->created_at?->format('M j, Y H:i') ?? '—' }}

{{ __('Campaign analytics') }}

{{ __('Full delivery, engagement, recipient, reply, and error performance for this WhatsApp broadcast.') }}

{{-- Header tiles. ROI/Audience/Cost/CPC/Quality are derived in the controller from campaign counters + the recipient log's contact-group makeup. The "Device" tile is read directly off the campaign->device relation (TODO: replace with real cost-tracking + revenue tables when those land). --}}
{{ __('ROI score') }}
{{ $header['roi'] }}
{{ __('Audience') }}
{{ $header['audience'] }}
{{ __('Device') }}
{{ $campaign->device_id ? '#' . $campaign->device_id : '—' }}
{{ __('Cost') }}
{{ $header['cost'] }}
{{ __('CPC') }}
{{ $header['cpc'] }}
{{ __('Quality') }}
{{ $header['quality'] }}
@php $deliveredPct = $campaign->total_recipients > 0 ? ($campaign->delivered_count / max($campaign->total_recipients, 1)) * 100 : 0; $readPct = $campaign->delivered_count > 0 ? ($campaign->read_count / max($campaign->delivered_count, 1)) * 100 : 0; $replyPct = $campaign->total_recipients > 0 ? ($campaign->responded_count / max($campaign->total_recipients, 1)) * 100 : 0; $clickPct = $campaign->total_recipients > 0 ? ($campaign->clicked_count / max($campaign->total_recipients, 1)) * 100 : 0; $failedPct = $campaign->total_recipients > 0 ? ($campaign->failed_count / max($campaign->total_recipients, 1)) * 100 : 0; @endphp {{-- KPI tiles — every counter + percent span carries a data-wac-detail-totals hook so user-wa-campaigns-detail.js can repaint live as the Node bridge fires delivered / read / failed callbacks. data-campaign-id on the wrapper lets the JS find the right id even if the URL is rewritten. --}}
{{ __('Total recipients') }}
{{ number_format($campaign->total_recipients) }}
{{ __('Audience size after duplicate cleanup') }}
{{ __('Delivered') }}
{{ number_format($campaign->delivered_count) }}
{{ number_format($deliveredPct, 1) }}% delivery rate
{{ __('Read') }}
{{ number_format($campaign->read_count) }}
{{ number_format($readPct, 1) }}% of delivered
{{ __('Replies') }}
{{ number_format($campaign->responded_count) }}
{{ number_format($replyPct, 1) }}% conversation rate
{{ __('Button taps') }}
{{ number_format($campaign->clicked_count) }}
{{ number_format($clickPct, 1) }}% of total
{{ __('Failed') }}
{{ number_format($campaign->failed_count) }}
{{ number_format($failedPct, 1) }}% failure rate
{{ __('Delivery curve') }}

{{ __('Sent, delivered, read') }}

Sent Delivered Read
{{ __('Outcome mix') }}

{{ __('Final status') }}

{{ __('Conversion funnel') }}

{{ __('From send to reply') }}

{{ __('Recipients') }}{{ number_format($funnel['recipients']) }}
{{ __('Delivered') }}{{ number_format($funnel['delivered']) }} / {{ $funnel['delivered_pct'] }}%
{{ __('Read') }}{{ number_format($funnel['read']) }} / {{ $funnel['read_pct'] }}%
{{ __('Clicked') }}{{ number_format($funnel['clicked']) }} / {{ $funnel['clicked_pct'] }}%
{{ __('Replied') }}{{ number_format($funnel['replied']) }} / {{ $funnel['replied_pct'] }}%
{{ __('Active hours') }}

{{ __('Read heatmap') }}

{{ __('Best segments') }}

{{ __('Top performers') }}

@if (empty($segments))
{{ __('No segment-level data yet. Once recipients reply or read, the breakdown by contact group will appear here.') }}
@else
@foreach ($segments as $i => $seg)
{{ $seg['name'] }} {{ $seg['read_pct'] }}% read
{{ number_format($seg['recipients']) }} recipient{{ $seg['recipients'] === 1 ? '' : 's' }}, {{ number_format($seg['replies']) }} repl{{ $seg['replies'] === 1 ? 'y' : 'ies' }}, {{ $seg['recipients'] > 0 ? round(($seg['opt_outs'] / $seg['recipients']) * 100, 1) : 0 }}% opt-out.
@endforeach
@endif
@push('scripts') {{-- Server-rendered chart payload consumed by resources/js/charts/user-wa-campaigns-detail.js. --}} @endpush