@php // Every value below comes from the controller — no static demo data. $row = $row ?? null; $recipientSummary = $recipientSummary ?? '—'; $statusBadge = $statusBadge ?? [ 'cls' => 'bg-paper-100 text-ink-500', 'dot' => 'bg-ink-500/40', 'label' => 'Unknown', ]; $previewBody = $previewBody ?? null; $recipients = $recipients ?? collect(); $tabCounts = $tabCounts ?? ['all' => 0, 'sent' => 0, 'delivered' => 0, 'read' => 0, 'failed' => 0, 'pending' => 0]; if (!$row) { abort(404); } $tz = $row->timezone ?: 'UTC'; $scheduledLocal = $row->scheduled_time?->copy()->setTimezone($tz); $nextRunLocal = $row->next_run_at?->copy()->setTimezone($tz); $lastRunLocal = $row->last_run_at?->copy()->setTimezone($tz); $completedLocal = $row->completed_at?->copy()->setTimezone($tz); $isFinished = in_array($row->status, ['completed', 'cancelled', 'failed'], true); $isActive = in_array($row->status, ['scheduled', 'running'], true); $isPaused = $row->status === 'paused'; $totalRecipients = (int) ($row->total_recipients ?? 0) ?: $tabCounts['all']; $totalSent = $tabCounts['sent']; $totalDelivered = $tabCounts['delivered']; $totalRead = $tabCounts['read']; $totalFailed = $tabCounts['failed']; $totalPending = $tabCounts['pending']; $deliveryRate = $totalSent > 0 ? round(($totalDelivered / $totalSent) * 100, 1) : 0; $readRate = $totalSent > 0 ? round(($totalRead / $totalSent) * 100, 1) : 0; $failRate = $totalRecipients > 0 ? round(($totalFailed / $totalRecipients) * 100, 1) : 0; $progressPct = $totalRecipients > 0 ? min(100, round(($totalSent / $totalRecipients) * 100, 1)) : 0; $deviceLabel = $row->device?->id ? (trim((string) $row->device->device_name) ?: 'Device #' . $row->device->id) : '—'; $devicePhone = $row->device?->id ? '+' . ltrim((string) ($row->device->country_code ?? ''), '+') . ' ' . $row->device->phone_number : null; $templateName = $row->template?->id ? $row->template->template_name : null; $templateCat = $row->template?->id ? ($row->template->category ?: 'Marketing') : null; // Donut series — real counters [delivered, sent-not-delivered, failed, pending]. $donutSeries = [$totalDelivered, max(0, $totalSent - $totalDelivered), $totalFailed, $totalPending]; // Engagement-curve data: build buckets relative to the first sent_at // we see in the pivot. With zero activity yet the chart shows // empty-state. Cheap PHP-side rollup (max few hundred recipients). $bucketEdges = [0, 5, 15, 30, 60, 120, 240, 480, 720, 1440, 2880, 4320]; // minutes $bucketLabels = ['0m', '5m', '15m', '30m', '1h', '2h', '4h', '8h', '12h', '24h', '48h', '72h']; $reads = array_fill(0, count($bucketEdges), 0); $sentByBucket = array_fill(0, count($bucketEdges), 0); $firstSent = $recipients->whereNotNull('sent_at')->sortBy('sent_at')->first()?->sent_at; if ($firstSent) { foreach ($recipients as $r) { if ($r->sent_at) { $m = $firstSent->diffInMinutes($r->sent_at); for ($i = count($bucketEdges) - 1; $i >= 0; $i--) { if ($m >= $bucketEdges[$i]) { $sentByBucket[$i]++; break; } } } if ($r->read_at) { $m = $firstSent->diffInMinutes($r->read_at); for ($i = count($bucketEdges) - 1; $i >= 0; $i--) { if ($m >= $bucketEdges[$i]) { $reads[$i]++; break; } } } } // Cumulative (each bucket = running total). for ($i = 1, $n = count($reads); $i < $n; $i++) { $reads[$i] += $reads[$i - 1]; $sentByBucket[$i] += $sentByBucket[$i - 1]; } } $engagementHasData = $totalSent > 0 || $totalRead > 0; @endphp
{{ __('Scheduled / Analytics') }}
{{ __('Schedule') }} {{ __('analytics') }}
{{ $statusBadge['label'] }}
{{-- ─────────── Header card ─────────── --}}
{{ __('Scheduled message') }}

{{ $row->schedule_name ?: 'Untitled' }}

@if ($templateName) Template · {{ $templateName }} @else {{ ucfirst($row->template_type ?: 'text') }} @endif {{ ucfirst($row->recipient_type) }} · {{ $recipientSummary }} @if ($scheduledLocal) {{ $scheduledLocal->format('M j · H:i') }} {{ $tz }} @endif @if ($row->is_recurring) Recurs · {{ $row->repeat_interval }} × {{ $row->repeat_every ?: 1 }} @endif {{ $deviceLabel }} @if ($devicePhone) · {{ $devicePhone }} @endif @if ($isActive && $nextRunLocal) Fires {{ $nextRunLocal->diffForHumans() }} @endif
@if ($isActive) @elseif ($isPaused) @endif @if (!$isFinished) @endif
{{-- ─────────── KPI strip — every cell driven by the pivot ─────────── --}}
{{ __('Recipients') }} {{ __('target') }}
{{ number_format($totalRecipients) }} {{ ucfirst($row->recipient_type) }}
{{ __('Delivered') }} {{ $deliveryRate }}%
{{ number_format($totalDelivered) }} {{ number_format($totalFailed) }} {{ __('failed') }}
{{ __('Read') }} {{ $readRate }}%
{{ number_format($totalRead) }} {{ __('of sent') }}
{{ __('Sent') }} {{ $progressPct }}%
{{ number_format($totalSent) }} {{ number_format($totalPending) }} {{ __('pending') }}
{{ __('Status') }} @if ($row->is_recurring) {{ __('recurring') }} @endif
{{ $statusBadge['label'] }}
@if ($isActive && $nextRunLocal) fires {{ $nextRunLocal->diffForHumans() }} @elseif ($isPaused) paused @elseif ($completedLocal) done {{ $completedLocal->diffForHumans() }} @else— @endif
{{-- ─────────── Engagement curve + Delivery funnel ─────────── --}}
{{ __('Engagement curve') }}

{{ __('Reads & sends after fire') }}

@if ($engagementHasData)
@else
{{ __('No engagement data yet') }}
{{ __('Curve fills in once the bot starts firing.') }}
@endif
{{ __('Delivery funnel') }}

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

{{ __('Recipients') }}{{ number_format($totalRecipients) }}
{{ __('Sent') }}{{ number_format($totalSent) }} / {{ $progressPct }}%
{{ __('Delivered') }}{{ number_format($totalDelivered) }} / {{ $deliveryRate }}%
{{ __('Read') }}{{ number_format($totalRead) }} / {{ $readRate }}%
Failed{{ number_format($totalFailed) }} / {{ $failRate }}%
{{-- ─────────── Status mix + Tabbed Recipient table ─────────── --}}
{{ __('Status mix') }}

{{ __('Delivery status') }}

@if (array_sum($donutSeries) === 0)
{{ __('No send activity yet') }}
{{ __('Donut fills in once the bot fires.') }}
@else
@endif
Delivered{{ number_format($totalDelivered) }}
Sent · not delivered{{ number_format(max(0, $totalSent - $totalDelivered)) }}
Failed{{ number_format($totalFailed) }}
Pending{{ number_format($totalPending) }}
{{-- Tabbed recipient table. Server renders all rows; JS filters by status. data-status maps the tabs (sent includes delivered/read; delivered includes read). Tabs match the KPI strip's semantics. --}}
{{ __('Recipients') }}

{{ __('Who got what') }}

@if ($recipients->isEmpty())
{{ __('No recipients on file for this schedule.') }}
@else @foreach ($recipients as $rec) @php $contact = $rec->contact?->id ? $rec->contact : null; $name = $contact?->name ?: ($contact?->first_name ?: '—'); $statusBadgePill = match ($rec->status) { 'pending' => ['cls' => 'bg-paper-100 text-ink-600', 'label' => 'Pending'], 'sent' => ['cls' => 'bg-accent-amber/15 text-[#7B5A14]', 'label' => 'Sent'], 'delivered' => [ 'cls' => 'bg-wa-teal/15 text-wa-teal', 'label' => 'Delivered', ], 'read' => ['cls' => 'bg-wa-deep/10 text-wa-deep', 'label' => 'Read'], 'failed' => [ 'cls' => 'bg-accent-coral/15 text-accent-coral', 'label' => 'Failed', ], default => ['cls' => 'bg-paper-100 text-ink-500', 'label' => $rec->status], }; $when = $rec->failed_at ?? ($rec->read_at ?? ($rec->delivered_at ?? $rec->sent_at)); @endphp @endforeach
{{ __('Recipient') }} {{ __('Phone') }} {{ __('Status') }} {{ __('When') }} {{ __('Note') }}
{{ $name }}
+{{ $rec->phone }} {{ $statusBadgePill['label'] }} {{ $when ? $when->copy()->setTimezone($tz)->format('M j · H:i') : '—' }} {{ $rec->error_message ?: ($rec->wa_message_id ? 'msg ' . substr($rec->wa_message_id, 0, 8) . '…' : '—') }}
@endif
{{-- ─────────── Schedule details + Message preview ─────────── --}}
{{ __('Schedule details') }}

{{ __('Configuration') }}

{{ __('Schedule type') }}
{{ ucfirst($row->schedule_type ?: 'once') }}
{{ __('Message type') }}
{{ ucfirst($row->template_type ?: 'text') }}
@if ($scheduledLocal)
{{ __('Scheduled at') }}
{{ $scheduledLocal->format('Y-m-d H:i') }} {{ $tz }}
@endif @if ($nextRunLocal)
{{ __('Next run') }}
{{ $nextRunLocal->format('Y-m-d H:i') }} {{ $tz }}
@endif @if ($lastRunLocal)
{{ __('Last run') }}
{{ $lastRunLocal->format('Y-m-d H:i') }} {{ $tz }}
@endif @if ($row->is_recurring)
{{ __('Repeats every') }}
{{ $row->repeat_every ?: 1 }} {{ $row->repeat_interval ?: 'day' }}(s)
@endif
{{ __('From device') }}
{{ $deviceLabel }}
@if ($devicePhone)
{{ __('Sender phone') }}
{{ $devicePhone }}
@endif @if ($row->node_schedule_id)
{{ __('Node schedule id') }}
{{ $row->node_schedule_id }}
@endif
{{ __('Message preview') }}
{{ __('What recipients see') }}
{{ $previewBody ?: '(empty body)' }}
@if ($templateName)
Template · {{ $templateName }}{{ $templateCat ? ' · ' . $templateCat : '' }}
@endif @if (!$isFinished) {{ __('Schedule another') }} @endif
{{-- Confirmation modal — shared with index page. --}}