Updated Dashboard and clarified default states.
This commit is contained in:
parent
260e017f79
commit
e7b25ea0c6
5 changed files with 172 additions and 35 deletions
|
|
@ -4,13 +4,54 @@
|
|||
|
||||
let { session } = $props()
|
||||
|
||||
const attendees = liveQuery(() => db.attendees.toArray())
|
||||
const event = liveQuery(() => db.event.get(1))
|
||||
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 total = $derived(($attendees ?? []).length)
|
||||
const checkedIn = $derived(($attendees ?? []).filter(a => a.checked_in).length)
|
||||
const remaining = $derived(total - checkedIn)
|
||||
const pct = $derived(total > 0 ? Math.round((checkedIn / total) * 100) : 0)
|
||||
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">
|
||||
|
|
@ -28,35 +69,113 @@
|
|||
</p>
|
||||
{/if}
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Total</div>
|
||||
<div class="stat-value">{total}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Checked in</div>
|
||||
<div class="stat-value" style="color:var(--c-success)">{checkedIn}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Remaining</div>
|
||||
<div class="stat-value">{remaining}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Progress</div>
|
||||
<div class="stat-value">{pct}%</div>
|
||||
</div>
|
||||
</div>
|
||||
{#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}
|
||||
|
||||
{#if total > 0}
|
||||
<div class="card" style="margin-bottom:1rem">
|
||||
<div style="height:8px;background:var(--c-border);border-radius:99px;overflow:hidden">
|
||||
<div style="height:100%;width:{pct}%;background:var(--c-success);border-radius:99px;transition:width 0.4s ease"></div>
|
||||
<!-- 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}
|
||||
|
||||
<p class="text-muted" style="font-size:0.85rem">
|
||||
<!-- 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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue