Set up Unconfirmed -> Registered -> Confirmed -> Ready flow for Volunteers
This commit is contained in:
parent
62b3dece84
commit
72b245d6d6
7 changed files with 95 additions and 20 deletions
|
|
@ -79,6 +79,7 @@ export const api = {
|
|||
update: (id, data) => apiJSON(`/api/volunteers/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
|
||||
delete: (id) => apiFetch(`/api/volunteers/${id}`, { method: 'DELETE' }),
|
||||
checkIn: (id) => apiJSON(`/api/volunteers/${id}/checkin`, { method: 'POST' }),
|
||||
confirm: (id) => apiJSON(`/api/volunteers/${id}/confirm`, { method: 'POST' }),
|
||||
assignShift: (id, shiftId) => apiFetch(`/api/volunteers/${id}/shifts`, { method: 'POST', body: JSON.stringify({ shift_id: shiftId }) }),
|
||||
unassignShift: (id, shiftId) => apiFetch(`/api/volunteers/${id}/shifts/${shiftId}`, { method: 'DELETE' }),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -129,9 +129,10 @@ tr:hover td { background: rgba(255,255,255,0.02); }
|
|||
font-size: 0.72rem; font-weight: 600;
|
||||
text-transform: uppercase; letter-spacing: 0.04em;
|
||||
}
|
||||
.badge-checked { background: rgba(34,197,94,0.15); color: var(--c-success); }
|
||||
.badge-confirmed { background: rgba(99,102,241,0.15); color: var(--c-accent-h); }
|
||||
.badge-unchecked { background: rgba(122,127,150,0.15); color: var(--c-muted); }
|
||||
.badge-checked { background: rgba(34,197,94,0.15); color: var(--c-success); }
|
||||
.badge-confirmed { background: rgba(99,102,241,0.15); color: var(--c-accent-h); }
|
||||
.badge-registered { background: rgba(14,165,233,0.15); color: #0ea5e9; }
|
||||
.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-lead { background: rgba(245,158,11,0.15); color: var(--c-warn); }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
let search = $state('')
|
||||
let filterDept = $state('')
|
||||
let filterChecked = $state('')
|
||||
let filterStatus = $state('')
|
||||
let error = $state('')
|
||||
let showAdd = $state(false)
|
||||
let adding = $state(false)
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
const role = $derived(session?.user?.role ?? '')
|
||||
const canManage = $derived(['admin', 'ticketing', 'staffing', 'colead'].includes(role))
|
||||
const canConfirm = $derived(['admin', 'staffing', 'colead'].includes(role))
|
||||
const myDeptIDs = $derived(session?.user?.department_ids ?? [])
|
||||
|
||||
// Auto-filter coleads to their department on mount
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
db.volunteers.filter(v => !v.deleted_at).toArray()
|
||||
)
|
||||
const allParticipants = liveQuery(() => db.participants.toArray())
|
||||
const allTickets = liveQuery(() => db.tickets.filter(t => !t.deleted_at).toArray())
|
||||
const allDepts = liveQuery(() =>
|
||||
db.departments.filter(d => !d.deleted_at).toArray()
|
||||
.then(arr => arr.sort((a, b) => a.name.localeCompare(b.name)))
|
||||
|
|
@ -44,8 +46,10 @@
|
|||
return list
|
||||
.filter(v => {
|
||||
if (filterDept && v.department_id !== parseInt(filterDept)) return false
|
||||
if (filterChecked === 'true' && !v.checked_in) return false
|
||||
if (filterChecked === 'false' && v.checked_in) return false
|
||||
if (filterStatus === 'unconfirmed' && v.email_confirmed) return false
|
||||
if (filterStatus === 'registered' && (!v.email_confirmed || v.confirmed)) return false
|
||||
if (filterStatus === 'confirmed' && (!v.confirmed || v.checked_in)) return false
|
||||
if (filterStatus === 'ready' && !v.checked_in) return false
|
||||
if (s && !v.name.toLowerCase().includes(s) &&
|
||||
!(v.email || '').toLowerCase().includes(s)) return false
|
||||
return true
|
||||
|
|
@ -62,6 +66,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function confirmVolunteer(v) {
|
||||
try {
|
||||
const updated = await api.volunteers.confirm(v.id)
|
||||
await db.volunteers.put(updated)
|
||||
} catch (err) {
|
||||
error = err.message
|
||||
}
|
||||
}
|
||||
|
||||
async function addVolunteer(e) {
|
||||
e.preventDefault()
|
||||
adding = true
|
||||
|
|
@ -110,6 +123,11 @@
|
|||
return ($allDepts ?? []).find(d => d.id === id)
|
||||
}
|
||||
|
||||
function participantHasTickets(participantId) {
|
||||
if (!participantId) return false
|
||||
return ($allTickets ?? []).some(t => t.participant_id === participantId)
|
||||
}
|
||||
|
||||
function participantFor(id) {
|
||||
return ($allParticipants ?? []).find(p => p.id === id) ?? null
|
||||
}
|
||||
|
|
@ -181,10 +199,12 @@
|
|||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
<select bind:value={filterChecked} style="width:auto">
|
||||
<option value="">All</option>
|
||||
<option value="false">Not ready</option>
|
||||
<option value="true">Ready</option>
|
||||
<select bind:value={filterStatus} style="width:auto">
|
||||
<option value="">All statuses</option>
|
||||
<option value="unconfirmed">Unconfirmed</option>
|
||||
<option value="registered">Registered</option>
|
||||
<option value="confirmed">Confirmed</option>
|
||||
<option value="ready">Ready</option>
|
||||
</select>
|
||||
<span class="text-muted" style="font-size:0.85rem;white-space:nowrap">
|
||||
{filtered.length} shown
|
||||
|
|
@ -219,7 +239,9 @@
|
|||
<span class="badge badge-lead" style="margin-left:0.4rem">Co-Lead</span>
|
||||
{/if}
|
||||
{#if !v.participant_id}
|
||||
<span class="badge badge-unchecked" style="margin-left:0.4rem" title="Not linked to a participant — no ticket record">No ticket</span>
|
||||
<span class="badge badge-unchecked" style="margin-left:0.4rem" title="Not linked to a participant">No ticket</span>
|
||||
{:else if !participantHasTickets(v.participant_id)}
|
||||
<span class="badge badge-partial" style="margin-left:0.4rem" title="Registered as volunteer but no ticket on file">No ticket</span>
|
||||
{/if}
|
||||
{#if v.email}
|
||||
<div class="text-muted" style="font-size:0.78rem">{v.email}</div>
|
||||
|
|
@ -236,9 +258,15 @@
|
|||
{/if}
|
||||
</td>
|
||||
<td class="td-status">
|
||||
<span class="badge {v.checked_in ? 'badge-checked' : v.email_confirmed ? 'badge-confirmed' : 'badge-unchecked'}">
|
||||
{v.checked_in ? 'Ready' : v.email_confirmed ? 'Confirmed' : 'Unconfirmed'}
|
||||
</span>
|
||||
{#if v.checked_in}
|
||||
<span class="badge badge-checked">Ready</span>
|
||||
{:else if v.confirmed}
|
||||
<span class="badge badge-confirmed">Confirmed</span>
|
||||
{:else if v.email_confirmed}
|
||||
<span class="badge badge-registered">Registered</span>
|
||||
{:else}
|
||||
<span class="badge badge-unchecked">Unconfirmed</span>
|
||||
{/if}
|
||||
{#if v.checked_in_at}
|
||||
<div class="text-muted" style="font-size:0.75rem">
|
||||
{new Date(v.checked_in_at).toLocaleTimeString()}
|
||||
|
|
@ -252,6 +280,9 @@
|
|||
</td>
|
||||
{#if canManage}
|
||||
<td class="td-actions">
|
||||
{#if canConfirm && v.email_confirmed && !v.confirmed && v.department_id}
|
||||
<button class="btn btn-primary btn-sm" onclick={() => confirmVolunteer(v)}>Confirm</button>
|
||||
{/if}
|
||||
<button class="btn btn-ghost btn-sm" onclick={() => toggleLead(v)}
|
||||
title={v.is_lead ? 'Remove co-lead' : 'Mark as co-lead'}>
|
||||
{v.is_lead ? '− Co-Lead' : '+ Co-Lead'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue