Tweaked Participants and Settings pages

This commit is contained in:
Pen Anderson 2026-03-04 14:31:52 -06:00
parent 3906b73c61
commit 219acb62d6
3 changed files with 75 additions and 20 deletions

View file

@ -131,6 +131,7 @@ tr:hover td { background: rgba(255,255,255,0.02); }
} }
.badge-checked { background: rgba(34,197,94,0.15); color: var(--c-success); } .badge-checked { background: rgba(34,197,94,0.15); color: var(--c-success); }
.badge-unchecked { background: rgba(122,127,150,0.15); color: var(--c-muted); } .badge-unchecked { background: rgba(122,127,150,0.15); color: var(--c-muted); }
.badge-partial { background: rgba(245,158,11,0.15); color: var(--c-warn); }
.badge-role { background: rgba(99,102,241,0.15); color: var(--c-accent-h); } .badge-role { background: rgba(99,102,241,0.15); color: var(--c-accent-h); }
.badge-lead { background: rgba(245,158,11,0.15); color: var(--c-warn); } .badge-lead { background: rgba(245,158,11,0.15); color: var(--c-warn); }

View file

@ -24,6 +24,15 @@
let newPronouns = $state('') let newPronouns = $state('')
let newNote = $state('') let newNote = $state('')
// Edit participant
let editId = $state(null)
let editName = $state('')
let editEmail = $state('')
let editPhone = $state('')
let editPronouns = $state('')
let editNote = $state('')
let saving = $state(false)
// Add ticket form (per participant) // Add ticket form (per participant)
let addTicketFor = $state(null) // participant id let addTicketFor = $state(null) // participant id
let addingTicket = $state(false) let addingTicket = $state(false)
@ -57,14 +66,8 @@
return ticketsFor(participantId).filter(t => t.checked_in_at).length return ticketsFor(participantId).filter(t => t.checked_in_at).length
} }
async function toggleExpand(id) { function toggleExpand(id) {
if (expandedId === id) { expandedId = expandedId === id ? null : id
expandedId = null
expandedTickets = []
return
}
expandedId = id
expandedTickets = ticketsFor(id)
} }
async function generateCodes() { async function generateCodes() {
@ -148,6 +151,32 @@
} }
} }
function startEdit(p) {
editId = p.id
editName = p.preferred_name
editEmail = p.email
editPhone = p.phone
editPronouns = p.pronouns
editNote = p.note
}
async function saveEdit(e) {
e.preventDefault()
saving = true; error = ''
try {
const p = await api.participants.update(editId, {
preferred_name: editName, email: editEmail, phone: editPhone,
pronouns: editPronouns, note: editNote,
})
await db.participants.put(p)
editId = null
} catch (err) {
error = err.message
} finally {
saving = false
}
}
async function addTicket(e, participantId) { async function addTicket(e, participantId) {
e.preventDefault() e.preventDefault()
addingTicket = true; error = '' addingTicket = true; error = ''
@ -283,6 +312,26 @@
{@const ci = checkedInCount(p.id)} {@const ci = checkedInCount(p.id)}
{@const isExpanded = expandedId === p.id} {@const isExpanded = expandedId === p.id}
{@const isMergeTarget = mergeMode && mergeSource?.id !== p.id} {@const isMergeTarget = mergeMode && mergeSource?.id !== p.id}
{@const isEditing = editId === p.id}
{#if isEditing}
<tr class="edit-row">
<td colspan={canManage ? 5 : 4}>
<form class="participant-edit-form" onsubmit={saveEdit}>
<div class="edit-fields">
<input bind:value={editName} placeholder="Preferred name" />
<input type="email" bind:value={editEmail} placeholder="Email" />
<input bind:value={editPhone} placeholder="Phone" />
<input bind:value={editPronouns} placeholder="Pronouns" />
<input bind:value={editNote} placeholder="Note" style="flex:2" />
</div>
<div class="actions" style="margin-top:0.5rem">
<button type="submit" class="btn btn-primary btn-sm" disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
<button type="button" class="btn btn-ghost btn-sm" onclick={() => editId = null}>Cancel</button>
</div>
</form>
</td>
</tr>
{:else}
<tr <tr
class:merge-target={isMergeTarget} class:merge-target={isMergeTarget}
onclick={mergeMode && mergeSource?.id !== p.id ? () => { mergeTarget = p } : null} onclick={mergeMode && mergeSource?.id !== p.id ? () => { mergeTarget = p } : null}
@ -297,7 +346,12 @@
<div class="text-muted" style="font-size:0.78rem">{p.note}</div> <div class="text-muted" style="font-size:0.78rem">{p.note}</div>
{/if} {/if}
</td> </td>
<td class="text-muted">{p.email || '—'}</td> <td class="text-muted">
{p.email || '—'}
{#if p.phone}
<div style="font-size:0.78rem">{p.phone}</div>
{/if}
</td>
<td> <td>
{#if pts.length > 0} {#if pts.length > 0}
<button class="btn btn-ghost btn-sm" onclick={(e) => { e.stopPropagation(); toggleExpand(p.id) }}> <button class="btn btn-ghost btn-sm" onclick={(e) => { e.stopPropagation(); toggleExpand(p.id) }}>
@ -320,13 +374,16 @@
{#if canManage} {#if canManage}
<td> <td>
{#if !mergeMode} {#if !mergeMode}
<button class="btn btn-ghost btn-sm" onclick={(e) => { e.stopPropagation(); startEdit(p) }}
title="Edit participant">✎</button>
<button class="btn btn-ghost btn-sm" onclick={(e) => { e.stopPropagation(); startMerge(p) }} <button class="btn btn-ghost btn-sm" onclick={(e) => { e.stopPropagation(); startMerge(p) }}
title="Merge this participant into another">⇄</button> title="Merge this participant into another">⇄</button>
{/if} {/if}
</td> </td>
{/if} {/if}
</tr> </tr>
{#if isExpanded} {/if}
{#if isExpanded && !isEditing}
<tr class="ticket-rows"> <tr class="ticket-rows">
<td colspan="5"> <td colspan="5">
<div class="ticket-list"> <div class="ticket-list">
@ -417,12 +474,9 @@
font-size: 0.825rem; font-size: 0.825rem;
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
} }
.badge-partial { .edit-row td { padding: 0.5rem 1rem; background: var(--c-bg); }
background: rgba(245,158,11,0.15); .participant-edit-form { display: flex; flex-direction: column; gap: 0.25rem; }
color: var(--c-warn); .edit-fields { display: flex; gap: 0.4rem; flex-wrap: wrap; }
padding: 0.15rem 0.5rem; .edit-fields input { flex: 1; min-width: 120px; font-size: 0.825rem; padding: 0.3rem 0.5rem; width: auto; }
border-radius: 99px;
font-size: 0.75rem;
font-weight: 600;
}
</style> </style>

View file

@ -232,8 +232,8 @@
Permanently delete all records of a given type. This cannot be undone. Permanently delete all records of a given type. This cannot be undone.
</p> </p>
<div style="display:flex;flex-wrap:wrap;gap:0.5rem"> <div style="display:flex;flex-wrap:wrap;gap:0.5rem">
<button class="btn btn-danger" onclick={() => resetModel('attendees', api.settings.resetAttendees)}> <button class="btn btn-danger" onclick={() => resetModel('tickets', api.settings.resetTickets)}>
Delete All Attendees Delete All Tickets
</button> </button>
<button class="btn btn-danger" onclick={() => resetModel('volunteers', api.settings.resetVolunteers)}> <button class="btn btn-danger" onclick={() => resetModel('volunteers', api.settings.resetVolunteers)}>
Delete All Volunteers Delete All Volunteers