player view donesies (vibe coding might be insane)

This commit is contained in:
2026-07-05 13:28:23 +01:00
parent ac4f02f597
commit 07c27ffaaa
4 changed files with 207 additions and 35 deletions

View File

@@ -230,6 +230,81 @@ export async function getRegisteredEventsWithPlayers(eventId?: number) {
return fullEventList; return fullEventList;
} }
export async function getPlayerEvents(playerId: number) {
const registrations = await getPlayerRegistrations(playerId);
if (registrations.length === 0) return [];
const events = await Promise.all(
registrations.map(async (reg) => {
const [event] = await db
.select()
.from(schema.registeredEventsView)
.where(eq(schema.registeredEventsView.eventId, reg.eventId));
if (!event) return null;
let awardedPoints = 0;
if (event.scorePreset && reg.placement > 0) {
const [pointsRow] = await db
.select()
.from(schema.scoringPresets)
.where(
and(
eq(schema.scoringPresets.presetID, event.scorePreset),
eq(schema.scoringPresets.placement, reg.placement)
)
);
if (pointsRow) awardedPoints = pointsRow.points;
}
const results = await db
.select()
.from(schema.registeredResults)
.where(eq(schema.registeredResults.registeredPlayerId, reg.registeredPlayerId));
let winnerInfo: { name: string; color: string; points: number } | null = null;
if (event.winner) {
const teamData = await getTeams(event.winner);
winnerInfo = teamData.teams[0] || null;
}
let unit = '';
if (event.resultPreset) {
const { resultPresets } = await getResultPreset(event.resultPreset);
if (resultPresets.length > 0) {
unit = resultPresets[0].unit;
}
}
return {
id: reg.eventId,
name: reg.eventName,
division: event.division,
state: event.state,
completed: event.timeCompleted || 0,
winner: winnerInfo,
bracket: reg.bracket,
placement: reg.placement,
points: awardedPoints,
unit,
results: results.map((r) => ({
index: r.resultIndex,
result: r.result
}))
};
})
);
return events
.filter((e) => e !== null)
.sort((a, b) => {
if (a.completed && b.completed) return b.completed - a.completed;
if (a.completed) return -1;
if (b.completed) return 1;
return b.id - a.id;
});
}
export async function getLedgerEntries(limit: number | 'all' = 100) { export async function getLedgerEntries(limit: number | 'all' = 100) {
let query = db let query = db
.select({ .select({

View File

@@ -0,0 +1,20 @@
import { getPlayerEvents } from '$lib/server/databaseManager';
import { globalEmitter } from '$lib/server/globalEmitter';
import { generateEndpoint } from '$lib/server/endpoint';
export async function GET({ params, request }) {
const playerId = parseInt(params.playerId);
const endpoint = generateEndpoint(async (enqueue) => {
let eventList = async () => {
let events = await getPlayerEvents(playerId);
enqueue(events);
};
eventList();
globalEmitter.on('eventUpdate', eventList);
return () => {
globalEmitter.off('eventUpdate', eventList);
};
}, request);
return (await endpoint).response;
}

View File

@@ -1,14 +1,14 @@
import { getPlayerInfo } from '$lib/server/databaseManager'; import { getPlayerInfo, getPlayerEvents } from '$lib/server/databaseManager';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
// export const load: PageServerLoad = async ({ params }) => {
// return {
// data: await getPlayerInfo(parseInt(params.playerId))
// };
// };
export const load: PageServerLoad = async ({ params }) => { export const load: PageServerLoad = async ({ params }) => {
const playerId = parseInt(params.playerId);
const [playerInfo, playerEvents] = await Promise.all([
getPlayerInfo(playerId),
getPlayerEvents(playerId)
]);
return { return {
playerInfo: getPlayerInfo(parseInt(params.playerId)) playerInfo,
playerEvents
}; };
}; };

View File

@@ -1,7 +1,29 @@
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy } from 'svelte';
import type { PageProps } from './$types'; import type { PageProps } from './$types';
let { data }: PageProps = $props(); let { data }: PageProps = $props();
let { playerInfo, playerEvents: initialEvents } = data;
let playerEvents = $state(initialEvents);
let eventEndpoint: EventSource;
onMount(() => {
eventEndpoint = new EventSource(`/api/playerEvents/${playerInfo.id}`);
eventEndpoint.onmessage = (e) => {
playerEvents = JSON.parse(e.data);
};
});
onDestroy(() => {
eventEndpoint?.close();
});
function ordinal(n: number) {
const s = ['th', 'st', 'nd', 'rd'];
const v = n % 100;
return n + (s[(v - 20) % 10] ?? s[v] ?? s[0]);
}
function marquee(node: HTMLElement) { function marquee(node: HTMLElement) {
function measure() { function measure() {
@@ -22,13 +44,9 @@
} }
</script> </script>
{#await data.playerInfo} <div class="page-container">
<div class="page-container"><div>Loading...</div></div>
{:then playerInfo}
<div class="page-container">
<div class="flex justify-center">
<div <div
class="player-box aspect-square w-full max-w-[95vw] md:aspect-2/1 lg:aspect-2/1" class="player-box !max-w-full w-full aspect-2/1 md:aspect-auto"
style="--c:{playerInfo.teamInfo.color}" style="--c:{playerInfo.teamInfo.color}"
> >
<div class="player-ghost" aria-hidden="true"> <div class="player-ghost" aria-hidden="true">
@@ -44,11 +62,70 @@
> >
</svg> </svg>
</div> </div>
<div class="goldman player-name-wrap text-5xl" use:marquee> <div
{playerInfo.firstName} class="relative z-1 flex flex-col justify-center h-full gap-0.5 px-5 py-3 md:flex-row md:items-center md:justify-between md:gap-0 md:px-6 md:py-4"
{playerInfo.lastName} >
<div class="goldman player-name-wrap text-3xl md:text-4xl lg:text-5xl" use:marquee>
{playerInfo.firstName} {playerInfo.lastName}
</div>
<div class="goldman text-lg md:text-right md:text-xl lg:text-2xl" style="opacity: 0.85;">
{playerInfo.teamInfo.name}
</div> </div>
</div> </div>
</div> </div>
<p class="section-label">Events</p>
<section class="events-scroll" style="box-shadow: none; max-height: none; overflow-y: visible;">
<div class="events">
{#each playerEvents as event (event.id)}
<div
class="event-card"
class:ongoing-event={event.state == 1}
class:completed-event={event.state == 2}
style={event.state == 2 && event.winner ? `--event-color: ${event.winner.color}` : ''}
>
<div class="event-header">
<a href="/event/{event.id}" class="event-name goldman">{event.name}</a>
<span class="event-division">{event.division}</span>
{#if event.state == 1}
<span class="event-status goldman">ONGOING</span>
{:else if event.state == 2}
<span
class="event-winner event-status goldman"
style={event.winner ? `--winner-color:${event.winner.color}` : ''}
>Won By {event.winner?.name ?? '?'}</span
>
{/if}
</div> </div>
{/await} <div class="flex flex-col gap-1.5 px-3 py-2 text-sm">
<div class="flex items-baseline gap-2">
<span class="text-xs opacity-50 goldman">{event.bracket}</span>
{#if event.placement > 0}
<div class="flex items-baseline gap-1">
<span class="text-xs opacity-50">placed</span>
<span class="goldman text-base">{ordinal(event.placement)}</span>
</div>
<div class="ml-auto flex items-baseline gap-1">
<span class="goldman text-base">{event.points}</span>
<span class="text-xs opacity-50">pts</span>
</div>
{:else}
<span class="text-xs opacity-40">Unranked</span>
{/if}
</div>
{#if event.results.length > 0}
<div class="text-xs opacity-70">
{#each event.results as score, i}
<span
>{i > 0 ? ' · ' : ''}Run {score.index + 1}: {score.result}{event.unit}</span
>
{/each}
</div>
{/if}
</div>
</div>
{/each}
</div>
</section>
</div>