199 lines
4.9 KiB
Svelte
199 lines
4.9 KiB
Svelte
<script lang="ts">
|
|
import { page } from "$app/stores";
|
|
import { Avatar } from "$lib/components/ui";
|
|
import type { Snippet } from "svelte";
|
|
import type { Event, EventMemberWithDetails, EventRole, EventDepartment } from "$lib/api/events";
|
|
import * as m from "$lib/paraglide/messages";
|
|
|
|
interface Props {
|
|
data: {
|
|
org: { id: string; name: string; slug: string };
|
|
userRole: string;
|
|
event: Event;
|
|
eventMembers: EventMemberWithDetails[];
|
|
eventRoles: EventRole[];
|
|
eventDepartments: EventDepartment[];
|
|
};
|
|
children: Snippet;
|
|
}
|
|
|
|
let { data, children }: Props = $props();
|
|
|
|
const basePath = $derived(
|
|
`/${data.org.slug}/events/${data.event.slug}`,
|
|
);
|
|
|
|
const modules = $derived([
|
|
{
|
|
href: basePath,
|
|
label: m.events_overview(),
|
|
icon: "dashboard",
|
|
exact: true,
|
|
},
|
|
{
|
|
href: `${basePath}/tasks`,
|
|
label: m.events_mod_tasks(),
|
|
icon: "task_alt",
|
|
},
|
|
{
|
|
href: `${basePath}/files`,
|
|
label: m.events_mod_files(),
|
|
icon: "folder",
|
|
},
|
|
{
|
|
href: `${basePath}/schedule`,
|
|
label: m.events_mod_schedule(),
|
|
icon: "calendar_today",
|
|
},
|
|
{
|
|
href: `${basePath}/budget`,
|
|
label: m.events_mod_budget(),
|
|
icon: "account_balance_wallet",
|
|
},
|
|
{
|
|
href: `${basePath}/guests`,
|
|
label: m.events_mod_guests(),
|
|
icon: "groups",
|
|
},
|
|
{
|
|
href: `${basePath}/team`,
|
|
label: m.events_mod_team(),
|
|
icon: "badge",
|
|
},
|
|
{
|
|
href: `${basePath}/sponsors`,
|
|
label: m.events_mod_sponsors(),
|
|
icon: "handshake",
|
|
},
|
|
]);
|
|
|
|
function isModuleActive(href: string, exact?: boolean): boolean {
|
|
if (exact) return $page.url.pathname === href;
|
|
return $page.url.pathname.startsWith(href);
|
|
}
|
|
|
|
function getStatusColor(status: string): string {
|
|
const map: Record<string, string> = {
|
|
planning: "bg-amber-400",
|
|
active: "bg-emerald-400",
|
|
completed: "bg-blue-400",
|
|
archived: "bg-light/40",
|
|
};
|
|
return map[status] ?? "bg-light/40";
|
|
}
|
|
|
|
function formatDateCompact(dateStr: string | null): string {
|
|
if (!dateStr) return "";
|
|
return new Date(dateStr).toLocaleDateString(undefined, {
|
|
month: "short",
|
|
day: "numeric",
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<div class="flex h-full">
|
|
<!-- Event Module Sidebar -->
|
|
<aside
|
|
class="w-56 shrink-0 bg-dark/30 border-r border-light/5 flex flex-col overflow-hidden"
|
|
>
|
|
<!-- Event Header -->
|
|
<div class="p-4 border-b border-light/5">
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<div
|
|
class="w-2.5 h-2.5 rounded-full shrink-0 {getStatusColor(
|
|
data.event.status,
|
|
)}"
|
|
></div>
|
|
<h2
|
|
class="text-body font-heading text-white truncate"
|
|
title={data.event.name}
|
|
>
|
|
{data.event.name}
|
|
</h2>
|
|
</div>
|
|
{#if data.event.start_date}
|
|
<p class="text-[11px] text-light/40 flex items-center gap-1">
|
|
<span
|
|
class="material-symbols-rounded"
|
|
style="font-size: 12px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 12;"
|
|
>calendar_today</span
|
|
>
|
|
{formatDateCompact(data.event.start_date)}{data.event
|
|
.end_date
|
|
? ` — ${formatDateCompact(data.event.end_date)}`
|
|
: ""}
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Module Navigation -->
|
|
<nav class="flex-1 flex flex-col gap-0.5 p-2 overflow-auto">
|
|
{#each modules as mod}
|
|
<a
|
|
href={mod.href}
|
|
class="flex items-center gap-2.5 px-3 py-2 rounded-xl text-body-sm font-body transition-colors {isModuleActive(
|
|
mod.href,
|
|
mod.exact,
|
|
)
|
|
? 'bg-primary text-background'
|
|
: 'text-light/60 hover:text-white hover:bg-dark/50'}"
|
|
>
|
|
<span
|
|
class="material-symbols-rounded"
|
|
style="font-size: 18px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 18;"
|
|
>{mod.icon}</span
|
|
>
|
|
{mod.label}
|
|
</a>
|
|
{/each}
|
|
</nav>
|
|
|
|
<!-- Event Team Preview -->
|
|
<div class="p-3 border-t border-light/5">
|
|
<p class="text-[11px] text-light/40 mb-2 px-1">
|
|
{m.events_team_count({ count: String(data.eventMembers.length) })}
|
|
</p>
|
|
<div class="flex flex-wrap gap-1 px-1">
|
|
{#each data.eventMembers.slice(0, 8) as member}
|
|
<div title={member.profile?.full_name || member.profile?.email || "Member"}>
|
|
<Avatar
|
|
name={member.profile?.full_name ||
|
|
member.profile?.email ||
|
|
"?"}
|
|
src={member.profile?.avatar_url}
|
|
size="xs"
|
|
/>
|
|
</div>
|
|
{/each}
|
|
{#if data.eventMembers.length > 8}
|
|
<div
|
|
class="w-6 h-6 rounded-full bg-dark flex items-center justify-center text-[10px] text-light/50"
|
|
>
|
|
+{data.eventMembers.length - 8}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Back link -->
|
|
<a
|
|
href="/{data.org.slug}/events"
|
|
class="flex items-center gap-2 px-4 py-3 border-t border-light/5 text-body-sm text-light/40 hover:text-white transition-colors"
|
|
>
|
|
<span
|
|
class="material-symbols-rounded"
|
|
style="font-size: 16px; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 16;"
|
|
>arrow_back</span
|
|
>
|
|
{m.events_all_events()}
|
|
</a>
|
|
</aside>
|
|
|
|
<!-- Module Content -->
|
|
<div class="flex-1 overflow-auto">
|
|
{#if children}
|
|
{@render children()}
|
|
{/if}
|
|
</div>
|
|
</div>
|