181 lines
6.3 KiB
Svelte
181 lines
6.3 KiB
Svelte
<script>
|
||
import { liveQuery } from 'dexie'
|
||
import { db } from '../db.js'
|
||
|
||
let { session } = $props()
|
||
|
||
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')
|
||
|
||
const event = liveQuery(() => db.event.get(1))
|
||
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)
|
||
|
||
// 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)
|
||
})
|
||
</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}
|
||
|
||
{#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>
|
||
</div>
|
||
|
||
{#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>
|
||
</div>
|
||
{/if}
|
||
|
||
<!-- 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>
|
||
</div>
|
||
</div>
|
||
{/if}
|
||
|
||
<!-- 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">
|
||
Welcome, <strong style="color:var(--c-text)">{session?.user?.username}</strong>
|
||
· <span class="badge badge-role">{session?.user?.role}</span>
|
||
</p>
|
||
</div>
|
||
|
||
<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>
|