@php /** * Rendered as inner-HTML of #sched-tbody both at first page load and * after silent AJAX refreshes (?partial=1). The helpers re-declared * here let the partial render standalone — the controller's JSON * path can call view()->render() without re-defining them. */ use Illuminate\Support\Carbon; /** @var \Illuminate\Support\Collection $rows */ $rows = $rows ?? collect(); $describeRecipients = $describeRecipients ?? function ($r) { $count = (int) ($r->total_recipients ?? 0); $tag = match ($r->recipient_type) { 'group' => 'Group', 'queue' => 'Queue', 'number' => 'Numbers', default => '—', }; return number_format($count) . ' / ' . $tag; }; $statusPill = $statusPill ?? function (string $status) { return match ($status) { 'scheduled' => ['cls' => 'bg-wa-mint text-wa-deep', 'dot' => 'bg-wa-green', 'label' => 'Queued'], 'running' => ['cls' => 'bg-[#D9E5F2] text-[#13478A]', 'dot' => 'bg-[#13478A]', 'label' => 'Running'], 'paused' => [ 'cls' => 'bg-accent-amber/20 text-[#7B5A14]', 'dot' => 'bg-accent-amber', 'label' => 'Paused', ], 'completed' => ['cls' => 'bg-paper-100 text-ink-700', 'dot' => 'bg-ink-500', 'label' => 'Done'], 'failed' => [ 'cls' => 'bg-accent-coral/15 text-accent-coral', 'dot' => 'bg-accent-coral', 'label' => 'Failed', ], 'cancelled' => ['cls' => 'bg-paper-100 text-ink-500', 'dot' => 'bg-ink-500/40', 'label' => 'Cancelled'], default => ['cls' => 'bg-paper-100 text-ink-700', 'dot' => 'bg-ink-500/40', 'label' => $status], }; }; $typePill = $typePill ?? function (string $type) { return match ($type) { 'template' => ['cls' => 'bg-wa-deep/10 text-wa-deep', 'icon' => 'M2.5 3.5h11v9h-11zM5 6h6M5 9h6'], 'media' => ['cls' => 'bg-accent-amber/20 text-[#7B5A14]', 'icon' => 'M2 3h12v10H2zM5 7l3 3 5-5'], 'location' => [ 'cls' => 'bg-[#F3E9FF] text-[#5B3D8A]', 'icon' => 'M8 1.5C5.5 1.5 3.5 3.4 3.5 5.7c0 3 4.5 8.8 4.5 8.8s4.5-5.8 4.5-8.8C12.5 3.4 10.5 1.5 8 1.5z', ], default => ['cls' => 'bg-paper-100 text-ink-700', 'icon' => 'M3 4h10M3 8h10M3 12h7'], }; }; @endphp @forelse ($rows as $row) @php $sp = $statusPill($row->status); $tp = $typePill($row->template_type); $tz = $row->timezone ?: 'UTC'; $whenLocal = Carbon::parse($row->next_run_at ?: $row->scheduled_time)->setTimezone($tz); // Carbon 3 returns floats from diffInMinutes — cast to int or the // sub-line renders as "in 3.8237903166667m". $diff = (int) round(now()->diffInMinutes($whenLocal, false)); $whenSub = $row->status === 'completed' && $row->completed_at ? 'completed ' . Carbon::parse($row->completed_at)->setTimezone($tz)->diffForHumans() : ($diff < 0 ? Carbon::parse($whenLocal)->diffForHumans() : ($diff < 1 ? 'now' : ($diff < 60 ? "in {$diff}m" : 'in ' . (int) round($diff / 60) . 'h'))) . ' · ' . $tz; $repeatLbl = $row->is_recurring ? $row->repeat_interval : 'once'; @endphp
{{ $row->schedule_name }}
{{ \Illuminate\Support\Str::limit($row->message_content, 60) ?: '—' }}
{{ ucfirst($row->template_type) }} {{ $describeRecipients($row) }}
{{ $whenLocal->format('M j · H:i') }}
{{ $whenSub }}
@if ($row->is_recurring) {{ $repeatLbl }} @else {{ __('once') }} @endif {{ $sp['label'] }} @if (in_array($row->status, ['scheduled', 'running'], true)) @elseif ($row->status === 'paused') @endif @if (in_array($row->status, ['scheduled', 'paused', 'running'], true)) {{-- Delete is only offered while the schedule is still actionable. Once it's completed/cancelled/failed the row collapses to just the eye — operator can still delete from the detail page if they really want. --}} @elseif ($row->status === 'failed') {{-- Retry icon next to the eye on failed rows. Clicking it opens a custom modal asking for a new send date / time / timezone, then resets the row to "scheduled" and re-registers it on Node. --}} @endif @if (in_array($row->status, ['completed', 'cancelled', 'failed'], true)) {{-- Terminal-state delete. Completed sends linger in the table forever otherwise — the operator needs a way to clean up old history without going to the detail page. Reuses the same `destroy` action wired by the JS so confirmation + row-removal logic stays unchanged. --}} @endif @empty @include('user.partials.empty-state', [ 'message' => 'No schedules match the current filters. Try clearing filters or schedule your first message.', 'resetHref' => url('/scheduled'), 'actionHref' => route('user.scheduled.create'), 'actionLabel' => 'Schedule message', ]) @endforelse