diff --git a/docs/USAGE.md b/docs/USAGE.md
index 949f71b..c08ec50 100644
--- a/docs/USAGE.md
+++ b/docs/USAGE.md
@@ -83,7 +83,7 @@ In **Settings**, the "Volunteer Signup" card controls:
In **Settings**, the "Shift Signups" card has an open/close toggle:
-- **Opening** signups generates kiosk codes for all confirmed volunteers and emails them their shift signup links. A confirmation dialog warns before sending.
+- **Opening** signups generates kiosk codes for all registered (email-confirmed) volunteers and emails them their shift signup links. A confirmation dialog warns before sending.
- **Closing** signups prevents new kiosk links from being issued on confirmation, but existing links continue to work.
If a volunteer confirms their email while signups are already open, they receive their kiosk link immediately in the confirmation response and via email.
@@ -92,9 +92,9 @@ If a volunteer confirms their email while signups are already open, they receive
Under **Volunteers**, you can:
-- Create volunteers manually (name, email, department)
-- Assign volunteers to departments
-- Mark volunteers as co-leads
+- Create volunteers manually (name, email, department, co-lead, note)
+- Edit existing volunteers (department, co-lead, note) via the inline Edit button
+- Confirm registered volunteers (admin, staffing, colead)
- Mark volunteers as ready (briefed at the volunteer station)
### Volunteer statuses
@@ -106,7 +106,7 @@ Under **Volunteers**, you can:
| **Confirmed** | Staff has verified the volunteer is assigned and ready to be scheduled | Admin, staffing, or co-lead |
| **Ready** | Briefed at the volunteer station, cleared to report for shifts | Gate staff at check-in |
-**Confirmation** is a deliberate staff action — it signals that a volunteer has been assigned to a department and you're expecting them. Only volunteers who have been assigned a department can be confirmed. Use the **Confirm** button on a registered volunteer's row.
+**Confirmation** is a deliberate staff action — it signals that you're expecting the volunteer for shifts. Use the **Confirm** button on a registered volunteer's row. Marking a volunteer as a co-lead (`is_lead`) automatically confirms them.
Volunteers are separate from participants. A person can be both a ticket holder and a volunteer. When a volunteer signs up via the public form, they are automatically linked to their participant record by email.
@@ -135,7 +135,7 @@ The Volunteer Kiosk is the public-facing flow for volunteers: signup, email conf
Kiosk links are generated and distributed automatically through the volunteer signup flow:
1. Volunteers sign up via the public signup form (`/volunteer-signup`) and confirm their email.
-2. In **Settings**, open shift signups. This generates kiosk codes for all confirmed volunteers and emails them their links. A confirmation dialog warns before sending.
+2. In **Settings**, open shift signups. This generates kiosk codes for all registered (email-confirmed) volunteers and emails them their links. A confirmation dialog warns before sending.
3. If a volunteer confirms their email while signups are already open, they receive their kiosk link immediately.
**Set base URL** — in Settings, set the public base URL (e.g., `https://turnpike.example.com`). Kiosk links use this URL.
diff --git a/frontend/src/pages/Volunteers.svelte b/frontend/src/pages/Volunteers.svelte
index e26ee6a..c8521e1 100644
--- a/frontend/src/pages/Volunteers.svelte
+++ b/frontend/src/pages/Volunteers.svelte
@@ -18,6 +18,12 @@
let newIsLead = $state(false)
let newNote = $state('')
+ let editID = $state(null)
+ let editDeptID = $state('')
+ let editIsLead = $state(false)
+ let editNote = $state('')
+ let saving = $state(false)
+
const role = $derived(session?.user?.role ?? '')
const canManage = $derived(['admin', 'ticketing', 'staffing', 'colead'].includes(role))
const canConfirm = $derived(['admin', 'staffing', 'colead'].includes(role))
@@ -100,15 +106,6 @@
}
}
- async function toggleLead(v) {
- try {
- const updated = await api.volunteers.update(v.id, { ...v, is_lead: !v.is_lead })
- await db.volunteers.put(updated)
- } catch (err) {
- error = err.message
- }
- }
-
async function deleteVolunteer(v) {
if (!confirm(`Delete volunteer "${v.name}"?`)) return
try {
@@ -119,6 +116,36 @@
}
}
+ function startEdit(v) {
+ editID = v.id
+ editDeptID = v.department_id ? String(v.department_id) : ''
+ editIsLead = v.is_lead
+ editNote = v.note ?? ''
+ }
+
+ function cancelEdit() {
+ editID = null
+ }
+
+ async function saveVolunteer(v) {
+ saving = true
+ error = ''
+ try {
+ const updated = await api.volunteers.update(v.id, {
+ ...v,
+ department_id: editDeptID ? parseInt(editDeptID) : null,
+ is_lead: editIsLead,
+ note: editNote,
+ })
+ await db.volunteers.put(updated)
+ editID = null
+ } catch (err) {
+ error = err.message
+ } finally {
+ saving = false
+ }
+ }
+
function deptFor(id) {
return ($allDepts ?? []).find(d => d.id === id)
}
@@ -231,66 +258,96 @@
{#each filtered as v (v.id)}
{@const dept = deptFor(v.department_id)}
- {@const participant = participantFor(v.participant_id)}
-
-
- {v.name}
- {#if v.is_lead}
- Co-Lead
- {/if}
- {#if !v.participant_id}
- No ticket
- {:else if !participantHasTickets(v.participant_id)}
- No ticket
- {/if}
- {#if v.email}
-