2026-03-03 11:27:07 -06:00
|
|
|
|
<script>
|
|
|
|
|
|
import { liveQuery } from 'dexie'
|
|
|
|
|
|
import { db } from '../db.js'
|
|
|
|
|
|
|
|
|
|
|
|
let { session } = $props()
|
|
|
|
|
|
|
2026-03-04 20:52:12 -06:00
|
|
|
|
const role = $derived(session?.user?.role ?? '')
|
|
|
|
|
|
const myDeptIDs = $derived(session?.user?.department_ids ?? [])
|
|
|
|
|
|
const isTicketing = $derived(['admin', 'ticketing'].includes(role))
|
|
|
|
|
|
const isStaffing = $derived(['admin', 'ticketing', 'staffing'].includes(role))
|
|
|
|
|
|
const isColead = $derived(role === 'colead')
|
|
|
|
|
|
|
2026-03-03 11:27:07 -06:00
|
|
|
|
const event = liveQuery(() => db.event.get(1))
|
2026-03-04 20:52:12 -06:00
|
|
|
|
const allTickets = liveQuery(() => db.tickets.toArray())
|
|
|
|
|
|
const allVolunteers = liveQuery(() => db.volunteers.filter(v => !v.deleted_at).toArray())
|
|
|
|
|
|
const allShifts = liveQuery(() => db.shifts.filter(s => !s.deleted_at).toArray())
|
|
|
|
|
|
const allDepts = liveQuery(() => db.departments.filter(d => !d.deleted_at).toArray())
|
|
|
|
|
|
const allVS = liveQuery(() => db.volunteer_shifts.toArray())
|
|
|
|
|
|
|
|
|
|
|
|
// Ticket stats
|
|
|
|
|
|
const tickets = $derived($allTickets ?? [])
|
|
|
|
|
|
const ticketTotal = $derived(tickets.length)
|
|
|
|
|
|
const ticketCheckedIn = $derived(tickets.filter(t => t.checked_in_at).length)
|
|
|
|
|
|
const ticketRemaining = $derived(ticketTotal - ticketCheckedIn)
|
|
|
|
|
|
const ticketPct = $derived(ticketTotal > 0 ? Math.round((ticketCheckedIn / ticketTotal) * 100) : 0)
|
|
|
|
|
|
|
|
|
|
|
|
// Volunteer stats (scoped for colead)
|
|
|
|
|
|
const volunteers = $derived.by(() => {
|
|
|
|
|
|
const vols = $allVolunteers ?? []
|
|
|
|
|
|
if (isColead) return vols.filter(v => myDeptIDs.includes(v.department_id))
|
|
|
|
|
|
return vols
|
|
|
|
|
|
})
|
|
|
|
|
|
const volTotal = $derived(volunteers.length)
|
|
|
|
|
|
const volCheckedIn = $derived(volunteers.filter(v => v.checked_in).length)
|
|
|
|
|
|
const volLeads = $derived(volunteers.filter(v => v.is_lead).length)
|
|
|
|
|
|
|
|
|
|
|
|
// Shift stats (scoped for colead)
|
|
|
|
|
|
const shifts = $derived.by(() => {
|
|
|
|
|
|
const all = $allShifts ?? []
|
|
|
|
|
|
if (isColead) return all.filter(s => myDeptIDs.includes(s.department_id))
|
|
|
|
|
|
return all
|
|
|
|
|
|
})
|
|
|
|
|
|
const shiftTotal = $derived(shifts.length)
|
|
|
|
|
|
const shiftsFilled = $derived.by(() => {
|
|
|
|
|
|
const vs = $allVS ?? []
|
|
|
|
|
|
return shifts.filter(s => vs.some(a => a.shift_id === s.id)).length
|
|
|
|
|
|
})
|
|
|
|
|
|
const shiftFillPct = $derived(shiftTotal > 0 ? Math.round((shiftsFilled / shiftTotal) * 100) : 0)
|
2026-03-03 11:27:07 -06:00
|
|
|
|
|
2026-03-04 20:52:12 -06:00
|
|
|
|
// Department names for colead header
|
|
|
|
|
|
const myDeptNames = $derived.by(() => {
|
|
|
|
|
|
const depts = $allDepts ?? []
|
|
|
|
|
|
return myDeptIDs.map(id => depts.find(d => d.id === id)?.name).filter(Boolean)
|
|
|
|
|
|
})
|
2026-03-03 11:27:07 -06:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="page">
|
|
|
|
|
|
<div class="page-header">
|
|
|
|
|
|
<h1 class="page-title">{$event?.name ?? 'Event'}</h1>
|
|
|
|
|
|
{#if $event?.venue}
|
|
|
|
|
|
<span class="text-muted">{$event.venue}</span>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{#if $event?.start_date}
|
|
|
|
|
|
<p class="text-muted" style="margin-bottom:1.5rem">
|
|
|
|
|
|
{$event.start_date}{$event.end_date !== $event.start_date ? ` – ${$event.end_date}` : ''}
|
|
|
|
|
|
{#if $event.timezone} · {$event.timezone}{/if}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
2026-03-04 20:52:12 -06:00
|
|
|
|
{#if isColead && myDeptNames.length > 0}
|
|
|
|
|
|
<p style="margin-bottom:1.5rem;font-size:0.9rem">
|
|
|
|
|
|
Your department{myDeptNames.length > 1 ? 's' : ''}:
|
|
|
|
|
|
<strong>{myDeptNames.join(', ')}</strong>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Ticket check-in (admin/ticketing) -->
|
|
|
|
|
|
{#if isTicketing}
|
|
|
|
|
|
<h2 class="dash-section">Ticket Check-in</h2>
|
|
|
|
|
|
<div class="stats">
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Total tickets</div>
|
|
|
|
|
|
<div class="stat-value">{ticketTotal}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Checked in</div>
|
|
|
|
|
|
<div class="stat-value" style="color:var(--c-success)">{ticketCheckedIn}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Remaining</div>
|
|
|
|
|
|
<div class="stat-value">{ticketRemaining}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Progress</div>
|
|
|
|
|
|
<div class="stat-value">{ticketPct}%</div>
|
|
|
|
|
|
</div>
|
2026-03-03 11:27:07 -06:00
|
|
|
|
</div>
|
2026-03-04 20:52:12 -06:00
|
|
|
|
|
|
|
|
|
|
{#if ticketTotal > 0}
|
|
|
|
|
|
<div style="height:8px;background:var(--c-border);border-radius:99px;overflow:hidden;margin-bottom:2rem">
|
|
|
|
|
|
<div style="height:100%;width:{ticketPct}%;background:var(--c-success);border-radius:99px;transition:width 0.4s ease"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Volunteer stats (admin/ticketing/staffing/colead) -->
|
|
|
|
|
|
{#if isStaffing || isColead}
|
|
|
|
|
|
<h2 class="dash-section">{isColead ? 'My Volunteers' : 'Volunteers'}</h2>
|
|
|
|
|
|
<div class="stats">
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Total</div>
|
|
|
|
|
|
<div class="stat-value">{volTotal}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Checked in</div>
|
|
|
|
|
|
<div class="stat-value" style="color:var(--c-success)">{volCheckedIn}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Leads</div>
|
|
|
|
|
|
<div class="stat-value">{volLeads}</div>
|
|
|
|
|
|
</div>
|
2026-03-03 11:27:07 -06:00
|
|
|
|
</div>
|
2026-03-04 20:52:12 -06:00
|
|
|
|
{/if}
|
2026-03-03 11:27:07 -06:00
|
|
|
|
|
2026-03-04 20:52:12 -06:00
|
|
|
|
<!-- Shift coverage (admin/ticketing/staffing/colead) -->
|
|
|
|
|
|
{#if isStaffing || isColead}
|
|
|
|
|
|
<h2 class="dash-section">{isColead ? 'My Shifts' : 'Shift Coverage'}</h2>
|
|
|
|
|
|
<div class="stats">
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Total shifts</div>
|
|
|
|
|
|
<div class="stat-value">{shiftTotal}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">With volunteers</div>
|
|
|
|
|
|
<div class="stat-value">{shiftsFilled}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="stat-label">Fill rate</div>
|
|
|
|
|
|
<div class="stat-value">{shiftFillPct}%</div>
|
2026-03-03 11:27:07 -06:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
2026-03-04 20:52:12 -06:00
|
|
|
|
<!-- Quick actions -->
|
|
|
|
|
|
{#if isTicketing}
|
|
|
|
|
|
<div class="dash-actions">
|
|
|
|
|
|
<a href="/import" class="btn btn-ghost btn-sm">Import CSV</a>
|
|
|
|
|
|
<a href="/participants" class="btn btn-ghost btn-sm">Manage Participants</a>
|
|
|
|
|
|
<a href="/settings" class="btn btn-ghost btn-sm">Settings</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{:else if isStaffing || isColead}
|
|
|
|
|
|
<div class="dash-actions">
|
|
|
|
|
|
<a href="/schedule" class="btn btn-ghost btn-sm">View Schedule</a>
|
|
|
|
|
|
<a href="/volunteers" class="btn btn-ghost btn-sm">Manage Volunteers</a>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
<p class="text-muted" style="font-size:0.85rem;margin-top:2rem">
|
2026-03-03 11:27:07 -06:00
|
|
|
|
Welcome, <strong style="color:var(--c-text)">{session?.user?.username}</strong>
|
|
|
|
|
|
· <span class="badge badge-role">{session?.user?.role}</span>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2026-03-04 20:52:12 -06:00
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
.dash-section {
|
|
|
|
|
|
font-size: 0.82rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
letter-spacing: 0.06em;
|
|
|
|
|
|
color: var(--c-muted);
|
|
|
|
|
|
margin-bottom: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.dash-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
margin-top: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|