Deploy Ladill Events / deploy (push) Successful in 35s
Give each area its own sidebar entry and hub pages, and walk new events through details, QR design, then review before publish. Co-authored-by: Cursor <cursoragent@cursor.com>
261 lines
8.8 KiB
PHP
261 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Events;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\QrCode;
|
|
use App\Models\QrEventRegistration;
|
|
use App\Notifications\EventProgrammeSharedNotification;
|
|
use App\Services\Billing\SmsService;
|
|
use App\Support\Events\EventBadgeZpl;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\Support\Facades\Notification;
|
|
use Illuminate\View\View;
|
|
|
|
class AttendeeController extends Controller
|
|
{
|
|
public function __construct(private readonly SmsService $sms) {}
|
|
|
|
public function hub(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->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<int, QrEventRegistration> */
|
|
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();
|
|
}
|
|
}
|