Files
ladill-qr-plus/resources/views/qr-codes/attendees.blade.php
T
isaaccladandCursor cd6571f199
Deploy Ladill QR Plus / deploy (push) Failing after 1s
Extract Ladill QR Plus as standalone app at qr.ladill.com.
Utility QR types only (URL, WiFi, link list, business, app download) with
SSO, Billing API integration, public /q resolver, and qr-plus:import for
platform migration. vCard and commerce types stay on the platform.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:31:24 +00:00

168 lines
12 KiB
PHP

<x-user-layout>
@php $isContribution = ($qrCode->content()['mode'] ?? 'ticketing') === 'contributions'; @endphp
<x-slot name="title">{{ $isContribution ? 'Contributions' : 'Attendees' }} {{ $qrCode->label }}</x-slot>
<div class="mx-auto max-w-5xl space-y-6"
x-data="{
selected: [],
toggle(id) { const i = this.selected.indexOf(id); i === -1 ? this.selected.push(id) : this.selected.splice(i, 1); },
get qs() { return this.selected.map(id => 'ids[]=' + id).join('&'); },
printSelected() { window.open(@js(route('qr-codes.badges', $qrCode)) + '?auto=1&' + this.qs, '_blank'); },
zplSelected() { window.location = @js(route('qr-codes.badges.zpl', $qrCode)) + '?' + this.qs; }
}">
{{-- Flash --}}
@foreach(['success', 'error'] as $flash)
@if(session($flash))
<div class="rounded-xl border px-4 py-3 text-sm {{ $flash === 'success' ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-red-200 bg-red-50 text-red-700' }}">
{{ session($flash) }}
</div>
@endif
@endforeach
{{-- Header --}}
<div>
<a href="{{ route('qr-codes.show', $qrCode) }}" class="inline-flex items-center gap-1 text-sm text-slate-500 hover:text-slate-700">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"/></svg>
{{ $qrCode->label }}
</a>
<h1 class="mt-1.5 text-2xl font-bold text-slate-900">{{ $isContribution ? 'Contributions' : 'Attendees' }}</h1>
</div>
{{-- Share programme outline --}}
@if($programme)
<div class="flex flex-col gap-3 rounded-2xl border border-indigo-100 bg-indigo-50/60 p-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex items-start gap-3">
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-indigo-100 text-indigo-600">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25Z"/></svg>
</span>
<div>
<p class="text-sm font-semibold text-slate-900">Programme outline</p>
<p class="text-xs text-slate-500">Email &amp; text the <span class="font-medium text-slate-600">{{ $programme->label }}</span> programme link to all confirmed attendees.</p>
</div>
</div>
<form method="POST" action="{{ route('qr-codes.attendees.share-programme', $qrCode) }}"
x-data="{ busy: false }"
@submit="if (!confirm('Send the programme outline to all confirmed attendees by email and SMS?')) { $event.preventDefault(); return; } busy = true;"
class="shrink-0">
@csrf
<button type="submit" :disabled="busy"
class="w-full rounded-xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-700 disabled:opacity-60 sm:w-auto">
<span x-text="busy ? 'Sending…' : 'Share with attendees'">Share with attendees</span>
</button>
</form>
</div>
@endif
{{-- Stats --}}
<div class="grid {{ $isContribution ? 'grid-cols-2' : 'grid-cols-3' }} gap-3">
<div class="rounded-2xl border border-slate-200 bg-white p-4">
<p class="text-2xl font-bold text-slate-900">{{ number_format($stats['total']) }}</p>
<p class="text-xs text-slate-500">{{ $isContribution ? 'Contributions' : 'Registered' }}</p>
</div>
@unless($isContribution)
<div class="rounded-2xl border border-slate-200 bg-white p-4">
<p class="text-2xl font-bold text-slate-900">{{ number_format($stats['checked_in']) }}</p>
<p class="text-xs text-slate-500">Checked in</p>
</div>
@endunless
<div class="rounded-2xl border border-slate-200 bg-white p-4">
<p class="text-2xl font-bold text-slate-900">GHS {{ number_format($stats['revenue'], 2) }}</p>
<p class="text-xs text-slate-500">{{ $isContribution ? 'Total raised' : 'Revenue' }}</p>
</div>
</div>
{{-- Toolbar --}}
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<form method="GET" class="flex items-center gap-2">
<input type="text" name="search" value="{{ request('search') }}" placeholder="Search name, email, code…"
class="rounded-xl border border-slate-200 px-3.5 py-2 text-sm focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">
<select name="status" onchange="this.form.submit()" class="rounded-xl border border-slate-200 px-3 py-2 text-sm text-slate-600 focus:border-indigo-400 focus:outline-none">
<option value="">All</option>
<option value="confirmed" @selected(request('status') === 'confirmed')>Confirmed</option>
<option value="pending" @selected(request('status') === 'pending')>Pending</option>
</select>
</form>
@unless($isContribution)
<div class="flex flex-wrap items-center gap-2">
<span x-show="selected.length" x-cloak class="text-xs font-medium text-slate-500"><span x-text="selected.length"></span> selected</span>
<button type="button" x-show="selected.length" x-cloak @click="printSelected()"
class="rounded-xl bg-indigo-600 px-3.5 py-2 text-xs font-semibold text-white hover:bg-indigo-700 transition">Print selected</button>
<button type="button" x-show="selected.length" x-cloak @click="zplSelected()"
class="rounded-xl border border-slate-200 px-3.5 py-2 text-xs font-semibold text-slate-600 hover:bg-slate-50 transition">ZPL selected</button>
<a href="{{ route('qr-codes.badges', $qrCode) }}?auto=1" target="_blank"
class="rounded-xl bg-slate-900 px-3.5 py-2 text-xs font-semibold text-white hover:bg-slate-800 transition">Print all badges</a>
<a href="{{ route('qr-codes.badges.zpl', $qrCode) }}"
class="rounded-xl border border-slate-200 px-3.5 py-2 text-xs font-semibold text-slate-600 hover:bg-slate-50 transition">Download ZPL</a>
</div>
@endunless
</div>
{{-- Table --}}
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead class="border-b border-slate-100 bg-slate-50/70 text-left text-xs uppercase tracking-wide text-slate-400">
<tr>
@unless($isContribution)<th class="px-4 py-3 w-8"></th>@endunless
<th class="px-4 py-3">{{ $isContribution ? 'Contributor' : 'Attendee' }}</th>
<th class="px-4 py-3">{{ $isContribution ? 'Category' : 'Ticket' }}</th>
@unless($isContribution)<th class="px-4 py-3">Badge</th>@endunless
<th class="px-4 py-3">Status</th>
@unless($isContribution)<th class="px-4 py-3 text-right">Check-in</th>@endunless
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
@forelse($registrations as $reg)
<tr class="hover:bg-slate-50/60">
@unless($isContribution)
<td class="px-4 py-3">
@if($reg->status === \App\Models\QrEventRegistration::STATUS_CONFIRMED)
<input type="checkbox" :checked="selected.includes({{ $reg->id }})" @change="toggle({{ $reg->id }})"
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
@endif
</td>
@endunless
<td class="px-4 py-3">
<p class="font-semibold text-slate-800">{{ $reg->attendee_name }}</p>
<p class="text-xs text-slate-400">{{ $reg->attendee_email }}</p>
@foreach(($reg->badge_fields ?? []) as $k => $v)
<span class="mr-1 inline-block rounded bg-slate-100 px-1.5 py-0.5 text-[10px] text-slate-500">{{ $k }}: {{ $v }}</span>
@endforeach
</td>
<td class="px-4 py-3">
<span class="font-medium text-slate-700">{{ $reg->tier_name }}</span>
@if($reg->isPaid())<p class="text-xs text-slate-400">GHS {{ number_format($reg->amountCedis(), 2) }}</p>@endif
</td>
@unless($isContribution)
<td class="px-4 py-3"><code class="rounded bg-slate-100 px-2 py-0.5 text-xs">{{ $reg->badge_code }}</code></td>
@endunless
<td class="px-4 py-3">
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium {{ $reg->status === 'confirmed' ? 'bg-emerald-50 text-emerald-700' : ($reg->status === 'pending' ? 'bg-amber-50 text-amber-700' : 'bg-slate-100 text-slate-500') }}">
{{ ucfirst($reg->status) }}
</span>
</td>
@unless($isContribution)
<td class="px-4 py-3 text-right">
<form method="POST" action="{{ route('qr-codes.attendees.check-in', [$qrCode, $reg]) }}">
@csrf @method('PATCH')
<button type="submit" class="rounded-lg border px-2.5 py-1 text-xs font-semibold transition {{ $reg->checked_in_at ? 'border-emerald-200 bg-emerald-50 text-emerald-700' : 'border-slate-200 text-slate-500 hover:bg-slate-50' }}">
{{ $reg->checked_in_at ? '✓ In' : 'Check in' }}
</button>
</form>
</td>
@endunless
</tr>
@empty
<tr><td colspan="{{ $isContribution ? 3 : 6 }}" class="px-4 py-12 text-center text-sm text-slate-400">{{ $isContribution ? 'No contributions yet.' : 'No registrations yet.' }}</td></tr>
@endforelse
</tbody>
</table>
</div>
@if($registrations->hasPages())
<div class="border-t border-slate-100 px-4 py-3">{{ $registrations->links() }}</div>
@endif
</div>
</div>
</x-user-layout>