Added Participant list to Gate kiosk.
This commit is contained in:
parent
d439306657
commit
62b3dece84
1 changed files with 61 additions and 6 deletions
|
|
@ -7,6 +7,8 @@
|
|||
let { session, onLogout } = $props()
|
||||
|
||||
let search = $state('')
|
||||
let manuallySelectedId = $state(null)
|
||||
let showAll = $state(false)
|
||||
let error = $state('')
|
||||
let scannerMsg = $state('')
|
||||
let qrSupported = $state(false)
|
||||
|
|
@ -44,22 +46,44 @@
|
|||
return ($tickets ?? []).find(t => t.external_id?.toLowerCase() === sl) ?? null
|
||||
})
|
||||
|
||||
// Name/email search across participants
|
||||
const allParticipantsSorted = $derived.by(() =>
|
||||
($participants ?? [])
|
||||
.filter(p => !p.deleted_at)
|
||||
.sort((a, b) => (a.preferred_name || a.email || '').localeCompare(b.preferred_name || b.email || ''))
|
||||
)
|
||||
|
||||
// Clear manual selection whenever search text changes
|
||||
$effect(() => {
|
||||
search
|
||||
manuallySelectedId = null
|
||||
})
|
||||
|
||||
// Name/email/ticket-name search across participants
|
||||
const filteredParticipants = $derived.by(() => {
|
||||
if (matchedTicket) return []
|
||||
const s = search.trim().toLowerCase()
|
||||
if (!s || s.length < 2) return []
|
||||
const byTicketName = new Set(
|
||||
($tickets ?? [])
|
||||
.filter(t => t.name?.toLowerCase().includes(s))
|
||||
.map(t => t.participant_id)
|
||||
.filter(Boolean)
|
||||
)
|
||||
return ($participants ?? [])
|
||||
.filter(p =>
|
||||
p.preferred_name?.toLowerCase().includes(s) ||
|
||||
p.email?.toLowerCase().includes(s)
|
||||
p.email?.toLowerCase().includes(s) ||
|
||||
byTicketName.has(p.id)
|
||||
)
|
||||
.sort((a, b) => (a.preferred_name || '').localeCompare(b.preferred_name || ''))
|
||||
.slice(0, 8)
|
||||
})
|
||||
|
||||
// Auto-select when exactly one participant matches
|
||||
// Manual selection takes priority; fall back to auto-select on single match
|
||||
const selectedParticipant = $derived.by(() => {
|
||||
if (manuallySelectedId) {
|
||||
return ($participants ?? []).find(p => p.id === manuallySelectedId) ?? null
|
||||
}
|
||||
if (filteredParticipants.length === 1) return filteredParticipants[0]
|
||||
return null
|
||||
})
|
||||
|
|
@ -165,6 +189,9 @@
|
|||
{scanning ? '■ Stop' : '⊡ Scan QR'}
|
||||
</button>
|
||||
{/if}
|
||||
<button class="gbtn gbtn-ghost" onclick={() => { showAll = !showAll; search = ''; manuallySelectedId = null }}>
|
||||
{showAll ? '✕ Close' : '☰ Browse'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if scanning}
|
||||
|
|
@ -211,7 +238,13 @@
|
|||
{:else if selectedParticipant}
|
||||
{@const pts = ticketsFor(selectedParticipant.id)}
|
||||
<div class="gate-match">
|
||||
<div class="gate-match-name-row">
|
||||
<div class="gate-match-name">{selectedParticipant.preferred_name || selectedParticipant.email || '(unknown)'}</div>
|
||||
{#if manuallySelectedId}
|
||||
<button class="gbtn gbtn-ghost" style="font-size:0.8rem;padding:0.3rem 0.6rem"
|
||||
onclick={() => manuallySelectedId = null}>← Back</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if selectedParticipant.email}
|
||||
<div class="gate-match-sub text-muted">{selectedParticipant.email}</div>
|
||||
{/if}
|
||||
|
|
@ -243,7 +276,7 @@
|
|||
{#each filteredParticipants as p}
|
||||
{@const pts = ticketsFor(p.id)}
|
||||
{@const ci = pts.filter(t => t.checked_in_at).length}
|
||||
<button class="gate-result-row" onclick={() => search = p.preferred_name || p.email || ''}>
|
||||
<button class="gate-result-row" onclick={() => manuallySelectedId = p.id}>
|
||||
<span>
|
||||
<strong>{p.preferred_name || p.email || '(unknown)'}</strong>
|
||||
{#if p.email && p.preferred_name}
|
||||
|
|
@ -261,6 +294,27 @@
|
|||
<div class="gate-msg gate-msg-warn">No matching participants or tickets found.</div>
|
||||
{/if}
|
||||
|
||||
<!-- Browse all participants -->
|
||||
{#if showAll && !matchedTicket && !selectedParticipant && search.trim().length < 2}
|
||||
<div class="gate-results">
|
||||
{#each allParticipantsSorted as p}
|
||||
{@const pts = ticketsFor(p.id)}
|
||||
{@const ci = pts.filter(t => t.checked_in_at).length}
|
||||
<button class="gate-result-row" onclick={() => { manuallySelectedId = p.id; showAll = false }}>
|
||||
<span>
|
||||
<strong>{p.preferred_name || p.email || '(unknown)'}</strong>
|
||||
{#if p.email && p.preferred_name}
|
||||
<span class="text-muted" style="font-size:0.8rem"> · {p.email}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="badge {ci === pts.length && pts.length > 0 ? 'badge-checked' : ci > 0 ? 'badge-partial' : 'badge-unchecked'}">
|
||||
{pts.length > 0 ? `${ci}/${pts.length}` : 'No ticket'}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Recent check-ins -->
|
||||
<div class="gate-recent">
|
||||
<div class="gate-recent-title">Recent Check-ins</div>
|
||||
|
|
@ -385,7 +439,8 @@
|
|||
padding: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.gate-match-name { font-size: 1.4rem; font-weight: 700; margin-bottom: 0.2rem; }
|
||||
.gate-match-name-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.2rem; }
|
||||
.gate-match-name { font-size: 1.4rem; font-weight: 700; }
|
||||
.gate-match-sub { color: var(--c-muted); font-size: 0.875rem; }
|
||||
.gate-party {
|
||||
margin: 0.5rem 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue