query('search', '')); $events = $account->qrCodes() ->where('type', QrCode::TYPE_EVENT) ->when($search !== '', fn ($q) => $q->where('label', 'like', "%{$search}%")) ->latest() ->get() ->map(function (QrCode $event) { $event->confirmed_count = $event->eventRegistrations() ->where('status', QrEventRegistration::STATUS_CONFIRMED) ->count(); $event->checked_in_count = $event->eventRegistrations() ->whereNotNull('checked_in_at') ->count(); return $event; }); return view('events.attendees-hub', [ 'events' => $events, 'search' => $search, ]); } public function badgesHub(Request $request): View { $account = ladill_account(); $search = trim((string) $request->query('search', '')); $events = $account->qrCodes() ->where('type', QrCode::TYPE_EVENT) ->when($search !== '', fn ($q) => $q->where('label', 'like', "%{$search}%")) ->latest() ->get() ->map(function (QrCode $event) { $event->printable_count = $event->eventRegistrations() ->where('status', QrEventRegistration::STATUS_CONFIRMED) ->count(); return $event; }); return view('events.badges-hub', [ 'events' => $events, 'search' => $search, ]); } public function badgePrinting(Request $request, QrCode $event): View { $this->authorize('view', $event); abort_unless($event->type === QrCode::TYPE_EVENT, 404); $isContribution = ($event->content()['mode'] ?? 'ticketing') === 'contributions'; if ($isContribution) { abort(404); } $query = $event->eventRegistrations() ->where('status', QrEventRegistration::STATUS_CONFIRMED) ->latest(); if ($search = trim((string) $request->query('search', ''))) { $query->where(function ($q) use ($search) { $q->where('attendee_name', 'like', "%{$search}%") ->orWhere('attendee_email', 'like', "%{$search}%") ->orWhere('badge_code', 'like', "%{$search}%"); }); } $registrations = $query->paginate(40)->withQueryString(); $badgeSize = $event->content()['badge_size'] ?? '4x3'; return view('events.badge-printing', [ 'qrCode' => $event, 'event' => $event, 'registrations' => $registrations, 'badgeSize' => $badgeSize, 'printableCount' => $event->eventRegistrations() ->where('status', QrEventRegistration::STATUS_CONFIRMED) ->count(), ]); } public function index(Request $request, QrCode $event): View { $this->authorize('view', $event); abort_unless($event->type === QrCode::TYPE_EVENT, 404); $query = $event->eventRegistrations()->latest(); if ($search = trim((string) $request->query('search', ''))) { $query->where(function ($q) use ($search) { $q->where('attendee_name', 'like', "%{$search}%") ->orWhere('attendee_email', 'like', "%{$search}%") ->orWhere('badge_code', 'like', "%{$search}%"); }); } if ($status = $request->query('status')) { $query->where('status', $status); } $registrations = $query->paginate(40)->withQueryString(); $stats = [ 'total' => $event->eventRegistrations()->where('status', QrEventRegistration::STATUS_CONFIRMED)->count(), 'checked_in' => $event->eventRegistrations()->whereNotNull('checked_in_at')->count(), 'revenue' => $event->eventRegistrations()->where('status', QrEventRegistration::STATUS_CONFIRMED)->sum('amount_minor') / 100, ]; $programme = $this->programmeFor($event); return view('events.attendees', [ 'qrCode' => $event, 'event' => $event, 'registrations' => $registrations, 'stats' => $stats, 'programme' => $programme, ]); } public function shareProgramme(QrCode $event): RedirectResponse { $this->authorize('view', $event); abort_unless($event->type === QrCode::TYPE_EVENT, 404); $programme = $this->programmeFor($event); if (! $programme) { return back()->with('error', 'No programme outline is attached to this event yet.'); } $recipients = $event->eventRegistrations() ->where('status', QrEventRegistration::STATUS_CONFIRMED) ->get(); $eventName = $event->content()['name'] ?? $event->label; $programmeUrl = $programme->publicUrl(); $emailed = 0; $texted = 0; foreach ($recipients as $reg) { if ($reg->attendee_email) { Notification::route('mail', $reg->attendee_email) ->notify(new EventProgrammeSharedNotification($event, $programme, $reg->attendee_name)); $emailed++; } if ($reg->attendee_phone) { $this->sms->send( $reg->attendee_phone, sprintf( 'Hi %s, the programme for %s is here: %s', explode(' ', $reg->attendee_name ?? 'there')[0], $eventName, $programmeUrl ) ); $texted++; } } if ($emailed === 0 && $texted === 0) { return back()->with('error', 'No confirmed attendees with contact details to share with yet.'); } return back()->with('success', "Programme shared — {$emailed} email(s) and {$texted} SMS sent."); } private function programmeFor(QrCode $event): ?QrCode { $programmeId = (int) ($event->content()['programme_qr_id'] ?? 0); if ($programmeId <= 0) { return null; } return QrCode::where('id', $programmeId) ->where('user_id', $event->user_id) ->where('type', QrCode::TYPE_ITINERARY) ->first(); } public function badges(Request $request, QrCode $event): View { $this->authorize('view', $event); abort_unless($event->type === QrCode::TYPE_EVENT, 404); $registrations = $this->selectedRegistrations($request, $event); return view('events.badges', [ 'qrCode' => $event, 'event' => $event, 'registrations' => $registrations, 'auto' => $request->boolean('auto'), ]); } public function badgesZpl(Request $request, QrCode $event): Response { $this->authorize('view', $event); abort_unless($event->type === QrCode::TYPE_EVENT, 404); $registrations = $this->selectedRegistrations($request, $event); $zpl = EventBadgeZpl::forRegistrations($event, $registrations); return response($zpl, 200, [ 'Content-Type' => 'application/octet-stream', 'Content-Disposition' => 'attachment; filename="badges-' . $event->short_code . '.zpl"', ]); } public function checkIn(QrCode $event, QrEventRegistration $registration): RedirectResponse { $this->authorize('view', $event); abort_unless($registration->qr_code_id === $event->id, 404); $registration->update([ 'checked_in_at' => $registration->checked_in_at ? null : now(), ]); return back(); } /** @return \Illuminate\Support\Collection */ private function selectedRegistrations(Request $request, QrCode $event) { $query = $event->eventRegistrations() ->where('status', QrEventRegistration::STATUS_CONFIRMED) ->latest(); $ids = array_filter((array) $request->query('ids', [])); if (! empty($ids)) { $query->whereIn('id', $ids); } return $query->get(); } }