Added ticket and participant creation. Revised Gate kiosk.

This commit is contained in:
Pen Anderson 2026-03-04 14:19:51 -06:00
parent d30ee18e77
commit 3906b73c61
5 changed files with 231 additions and 117 deletions

View file

@ -11,10 +11,25 @@
let generating = $state(false)
let emailing = $state(false)
let mergeMode = $state(false)
let mergeSource = $state(null) // participant being merged away
let mergeTarget = $state(null) // participant to keep
let mergeSource = $state(null)
let mergeTarget = $state(null)
let expandedId = $state(null)
let expandedTickets = $state([])
// Add participant form
let showAdd = $state(false)
let adding = $state(false)
let newName = $state('')
let newEmail = $state('')
let newPhone = $state('')
let newPronouns = $state('')
let newNote = $state('')
// Add ticket form (per participant)
let addTicketFor = $state(null) // participant id
let addingTicket = $state(false)
let newTicketName = $state('')
let newTicketType = $state('')
let newTicketExtId = $state('')
const role = $derived(session?.user?.role ?? '')
const canManage = $derived(['admin', 'ticketing'].includes(role))
@ -114,6 +129,45 @@
if (!ts) return ''
return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
}
async function addParticipant(e) {
e.preventDefault()
adding = true; error = ''
try {
const p = await api.participants.create({
preferred_name: newName, email: newEmail, phone: newPhone,
pronouns: newPronouns, note: newNote,
})
await db.participants.put(p)
showAdd = false
newName = newEmail = newPhone = newPronouns = newNote = ''
} catch (err) {
error = err.message
} finally {
adding = false
}
}
async function addTicket(e, participantId) {
e.preventDefault()
addingTicket = true; error = ''
try {
const tk = await api.tickets.create({
participant_id: participantId,
name: newTicketName,
ticket_type: newTicketType,
external_id: newTicketExtId,
source: 'manual',
})
await db.tickets.put(tk)
addTicketFor = null
newTicketName = newTicketType = newTicketExtId = ''
} catch (err) {
error = err.message
} finally {
addingTicket = false
}
}
</script>
<div class="page">
@ -121,6 +175,7 @@
<h1 class="page-title">Participants</h1>
<div class="actions">
{#if canManage}
<button class="btn btn-primary btn-sm" onclick={() => showAdd = !showAdd}>+ Add</button>
<a href="/api/participants/export" class="btn btn-ghost btn-sm">Export CSV</a>
<button class="btn btn-ghost btn-sm" onclick={generateCodes} disabled={generating}>
{generating ? '…' : '⚿ Generate Codes'}
@ -133,6 +188,41 @@
</div>
</div>
{#if showAdd && canManage}
<div class="card" style="margin-bottom:1.5rem">
<form onsubmit={addParticipant}>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
<div class="form-group">
<label for="p-name">Name</label>
<input id="p-name" bind:value={newName} placeholder="Preferred name" />
</div>
<div class="form-group">
<label for="p-email">Email</label>
<input id="p-email" type="email" bind:value={newEmail} placeholder="email@example.com" />
</div>
<div class="form-group">
<label for="p-phone">Phone</label>
<input id="p-phone" bind:value={newPhone} placeholder="Optional" />
</div>
<div class="form-group">
<label for="p-pronouns">Pronouns</label>
<input id="p-pronouns" bind:value={newPronouns} placeholder="Optional" />
</div>
</div>
<div class="form-group">
<label for="p-note">Note</label>
<input id="p-note" bind:value={newNote} placeholder="Optional note" />
</div>
<div class="actions">
<button type="submit" class="btn btn-primary" disabled={adding || (!newName && !newEmail)}>
{adding ? 'Adding…' : 'Add participant'}
</button>
<button type="button" class="btn btn-ghost" onclick={() => showAdd = false}>Cancel</button>
</div>
</form>
</div>
{/if}
{#if mergeMode && mergeSource}
<div class="card" style="margin-bottom:1.5rem;border-color:var(--c-accent)">
<div style="margin-bottom:0.75rem">
@ -236,7 +326,7 @@
</td>
{/if}
</tr>
{#if isExpanded && pts.length > 0}
{#if isExpanded}
<tr class="ticket-rows">
<td colspan="5">
<div class="ticket-list">
@ -270,6 +360,24 @@
</div>
</div>
{/each}
{#if canManage}
{#if addTicketFor === p.id}
<form class="ticket-add-form" onsubmit={(e) => addTicket(e, p.id)}>
<input bind:value={newTicketName} placeholder="Name on ticket (optional)" style="flex:2" />
<input bind:value={newTicketType} placeholder="Type (optional)" style="flex:1" />
<input bind:value={newTicketExtId} placeholder="External ID (optional)" style="flex:1" />
<button type="submit" class="btn btn-primary btn-sm" disabled={addingTicket}>
{addingTicket ? '…' : 'Add'}
</button>
<button type="button" class="btn btn-ghost btn-sm" onclick={() => addTicketFor = null}>Cancel</button>
</form>
{:else}
<button class="btn btn-ghost btn-sm" style="align-self:flex-start;margin-top:0.25rem"
onclick={() => { addTicketFor = p.id; newTicketName = newTicketType = newTicketExtId = '' }}>
+ Add ticket
</button>
{/if}
{/if}
</div>
</td>
</tr>
@ -294,6 +402,21 @@
background: var(--c-surface);
font-size: 0.875rem;
}
.ticket-add-form {
display: flex;
gap: 0.4rem;
align-items: center;
flex-wrap: wrap;
padding: 0.4rem 0.6rem;
background: var(--c-bg);
border-radius: 6px;
border: 1px dashed var(--c-border);
}
.ticket-add-form input {
min-width: 0;
font-size: 0.825rem;
padding: 0.3rem 0.5rem;
}
.badge-partial {
background: rgba(245,158,11,0.15);
color: var(--c-warn);