Compare commits

..

2 Commits

Author SHA1 Message Date
b7e060441c added unit to scoring page 2026-07-01 17:54:52 +01:00
3b964c4d9c player scores working yayyyy 2026-07-01 17:52:32 +01:00
9 changed files with 245 additions and 87 deletions

View File

@@ -1,5 +1,5 @@
import { db } from '$lib/server/db';
import { eq } from 'drizzle-orm';
import { sql, eq, and } from 'drizzle-orm';
import * as schema from '$lib/server/db/schema';
import { globalEmitter } from './globalEmitter';
@@ -74,7 +74,6 @@ export async function startEvent(eventId: number) {
.from(schema.registeredEventsView)
.where(eq(schema.registeredEventsView.eventId, eventId));
let requestedEvent = event[0];
console.log(requestedEvent);
if (requestedEvent.state != 0) {
console.log('not startable');
return false;
@@ -84,26 +83,26 @@ export async function startEvent(eventId: number) {
.set({ state: 1 })
.where(eq(schema.registeredEvents.id, requestedEvent.eventId))
.returning();
console.log(replacedEvent);
globalEmitter.emit('eventUpdate');
return true;
}
}
// Fetch all players registered for a specific event
export async function getAllRegisteredEventPlayers(eventId: number) {
export async function getAllRegisteredEventPlayers(eventId: number, getScores?: boolean) {
const eventPlayers = await db
.select()
.from(schema.registeredEventPlayersView)
// Filter by event ID
.where(eq(schema.registeredEventPlayersView.eventId, eventId))
.orderBy(
schema.registeredEventPlayersView.bracket,
schema.registeredEventPlayersView.placement,
sql`CASE WHEN ${schema.registeredEventPlayersView.placement} = 0 THEN 999999 ELSE ${schema.registeredEventPlayersView.placement} END ASC`,
schema.registeredEventPlayersView.teamName
);
return {
eventPlayers: eventPlayers.map((players) => ({
// 1. Wrap the map in Promise.all and await it
const resolvedPlayers = await Promise.all(
eventPlayers.map(async (players) => ({
id: players.playerId,
firstName: players.firstName,
lastName: players.lastName,
@@ -114,8 +113,14 @@ export async function getAllRegisteredEventPlayers(eventId: number) {
eventName: players.eventName,
teamId: players.teamId,
teamName: players.teamName,
teamColor: players.teamColor
teamColor: players.teamColor,
playerScores: getScores == true ? await getPlayerScores(players.playerId, eventId) : undefined
}))
);
// 2. Return the fully resolved data
return {
eventPlayers: resolvedPlayers
};
}
@@ -128,8 +133,40 @@ export async function getAllBrackets() {
}
export async function getPlayerInfo(playerId: number) {
const playerInfo = await db.select().from(schema.players).where(eq(schema.players.id), playerId);
return playerInfo;
const playerInfo = await db.select().from(schema.players).where(eq(schema.players.id, playerId));
const teamInfo = await db
.select()
.from(schema.teams)
.where(eq(schema.teams.id, playerInfo[0].team));
return { ...playerInfo[0], teamInfo: teamInfo[0] };
}
export async function getPlayerScores(playerId: number, eventId?: number) {
const playerRegistrations = await getPlayerRegistrations(playerId, eventId);
let scoresObject: any[] = [];
for (let registration in playerRegistrations) {
let currentReg = playerRegistrations[registration];
let scores = await db
.select()
.from(schema.registeredResults)
.where(eq(schema.registeredResults.registeredPlayerId, currentReg.registeredPlayerId));
scoresObject.push(...scores);
}
return scoresObject;
}
export async function getPlayerRegistrations(playerId: number, eventId?: number) {
const playerRegistrations = await db
.select()
.from(schema.registeredEventPlayersView)
.where(
and(
eq(schema.registeredEventPlayersView.playerId, playerId),
eventId ? eq(schema.registeredEventPlayersView.eventId, eventId) : undefined
)
);
return playerRegistrations;
}
export async function getResultPreset(presetId?: number) {
@@ -155,7 +192,10 @@ export async function getRegisteredEventsWithPlayers(eventId?: number) {
for (let registeredEvent in registeredEventList) {
let event = registeredEventList[registeredEvent];
let resultPreset = await getResultPreset(event.resultPreset);
let registeredPlayers = await getAllRegisteredEventPlayers(event.id);
let registeredPlayers = await getAllRegisteredEventPlayers(
event.id,
eventId != undefined ? true : undefined
);
// Group players by bracket category for the frontend
const bracketOrder = brackets.brackets.map((category) => {

View File

@@ -55,6 +55,19 @@ export async function POST({ request }: any) {
teamScores.set(currentPlayerTeam, currentTeamScore + score);
}
for (let result in currentPlayer.scores) {
let currentResult = currentPlayer.scores[result];
let newScoreEntry = await db
.insert(schema.registeredResults)
.values({
registeredPlayerId: currentPlayer.registeredPlayerId,
resultIndex: parseInt(result),
result: currentResult
})
.returning();
console.log(newScoreEntry);
}
// Update player placement in the database
let newPlayerPlacement = await db
.update(schema.registeredPlayers)

View File

@@ -0,0 +1,14 @@
import { eq } from 'drizzle-orm';
import { db } from '$lib/server/db';
import { scorers } from '$lib/server/db/schema';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals }) => {
if (!locals.user) return { user: null };
const [row] = await db
.select({ role: scorers.role })
.from(scorers)
.where(eq(scorers.id, locals.user.id));
return { user: { ...locals.user, role: row?.role ?? 'scorer' } };
};

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import type { PageProps } from './$types';
let { params }: PageProps = $props();
let { params, data }: PageProps = $props();
function ordinal(n: number) {
const s = ['th', 'st', 'nd', 'rd'];
@@ -75,12 +75,21 @@
<div>loading</div>
{:then eventData}
{@const event = eventData[0]}
{console.log(event)}
<div class="flex justify-center">
<div class="w-full flex-col px-[5vw] text-center">
<div
style:background-color={event.state === 1
? 'color-mix(in srgb, #fe640b 18%, transparent)'
: event.state === 2
? 'color-mix(in srgb, #a6e3a1 18%, transparent)'
: ''}
class="align-text-middle my-7 h-10 w-full rounded-2xl border-2 border-solid border-ctp-surface1"
>
{event.name} - {event.division}
{#if event.state == 1}- ONGOING
{:else if event.state == 2}- FINISHED
{/if}
</div>
{#each event.registeredPlayers as bracket, bi}
{#if bi > 0}
@@ -92,7 +101,7 @@
<div class="bracket-vertical-sep"></div>
</div>
{#each bracket.items as player}
<div class="player-box h-30" style="--c:{player.teamColor}">
<div class="player-box" style="--c:{player.teamColor}">
<div class="player-ghost" aria-hidden="true">
<svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="ghost-svg">
<text
@@ -114,6 +123,13 @@
</div>
{#if player.placement !== 0}
<div class="player-placement goldman">{ordinal(player.placement)}</div>
<div class="resultContainer flex justify-center">
{#each player.playerScores as score}
<div class="mx-5 my-1 rounded border-2" style="border-color:{player.teamColor}">
Run {score.resultIndex + 1}: {score.result}{event.resultPresets[0].unit}
</div>
{/each}
</div>
{:else}
<div class="player-placement-gap"></div>
{/if}
@@ -123,10 +139,23 @@
{/each}
</div>
</div>
<div class="mt-10 flex w-full justify-center">
<a
class="flex justify-center rounded border-2 border-solid border-white bg-ctp-surface2 p-4"
href="/event/scoring/{eventId}">Score This Event</a
>
</div>
{#if data.user}
<div class="mt-10 flex w-full justify-center">
<a
class="flex justify-center rounded border-2 border-solid border-white bg-ctp-surface2 p-4"
href="/event/scoring/{eventId}">Score This Event</a
>
</div>
{/if}
{/await}
<style>
.resultContainer {
flex-direction: column;
}
@media (max-width: 479px) {
.resultContainer {
flex-direction: column;
}
}
</style>

View File

@@ -126,7 +126,7 @@
headers: { 'Content-type': 'application/json; charset=UTF-8' }
});
const data = await response.json();
console.log(data);
console.log(data[0]);
event = data[0];
brackets = data[0].registeredPlayers.map((b: any) => ({
...b,
@@ -155,11 +155,16 @@
loading = false;
eventEndpoint = new EventSource('/api/registeredEvents');
eventEndpoint.onmessage = (e) => {
const data = JSON.parse(e.data)[eventId - 1];
console.log(data);
event = data;
brackets = data.registeredPlayers.map((b: any) => ({
eventEndpoint.onmessage = async (e) => {
const response = await fetch('/api/registeredEvents', {
method: 'POST',
body: JSON.stringify({ eventId }),
headers: { 'Content-type': 'application/json; charset=UTF-8' }
});
const data = await response.json();
console.log('sone', data[0]);
event = data[0];
brackets = data[0].registeredPlayers.map((b: any) => ({
...b,
items: [...b.items]
}));
@@ -266,11 +271,6 @@
>Start event</button
>
{/if}
<!-- <button onclick={() => (sortByScore = !sortByScore)}> -->
<!-- {sortByScore ? 'Sort: Score' : 'Sort: Manual'} -->
<!-- </button> -->
<div class="flex flex-row justify-center">
<div class="flex w-50 min-w-0 flex-col">
<div class="brackets-name text-bold">=</div>
@@ -333,37 +333,45 @@
{player.lastName}
</div>
<div class="result-input-containers flex flex-col">
{#each Array.from({ length: numResults }, (_, i) => i) as run}
<input
type="number"
placeholder="run {run + 1}"
class="text-black"
disabled={event.state != 1}
value={pendingScores[player.id]?.[run] ?? ''}
oninput={(e) => {
const current = [
...(pendingScores[player.id] ?? Array(numResults).fill(''))
];
current[run] = e.currentTarget.value;
pendingScores[player.id] = current;
}}
onblur={(e) => {
const val = parseFloat(e.currentTarget.value);
const current = [
...(committedScores[player.id] ?? Array(numResults).fill(null))
];
current[run] = isNaN(val) ? null : val;
committedScores[player.id] = current;
}}
/>
{/each}
{#if event.state == 1}
{#each Array.from({ length: numResults }, (_, i) => i) as run}
<input
type="number"
placeholder="run {run + 1}"
class="text-black"
disabled={event.state != 1}
value={pendingScores[player.id]?.[run] ?? ''}
oninput={(e) => {
const current = [
...(pendingScores[player.id] ?? Array(numResults).fill(''))
];
current[run] = e.currentTarget.value;
pendingScores[player.id] = current;
}}
onblur={(e) => {
const val = parseFloat(e.currentTarget.value);
const current = [
...(committedScores[player.id] ?? Array(numResults).fill(null))
];
current[run] = isNaN(val) ? null : val;
committedScores[player.id] = current;
}}
/>
{/each}
{#if event.resultPresets[0].averageResults == 1}
<div class="text-sm opacity-60">
avg: {avgScore.toFixed(2)}
</div>
{/if}
{:else if player.playerScores.length > 0}
{#each player.playerScores as score}
<div class="border-red border-2 px-2">
{score.result}{event.resultPresets[0].unit}
</div>
{/each}
{/if}
</div>
</div>
{#if event.resultPresets[0].averageResults == 1}
<div class="text-sm opacity-60">
avg: {avgScore.toFixed(2)}
</div>
{/if}
</div>
{/each}
</div>

View File

@@ -382,5 +382,5 @@
margin-top: 2px;
}
.player-placement-gap {
height: 20px;
height: 50px;
}

View File

@@ -1,7 +1,14 @@
import { getAllInitialInfo } from '$lib/server/databaseManager';
import { getPlayerInfo } from '$lib/server/databaseManager';
import type { PageServerLoad } from './$types';
// Provide initial data for the home page
export const load = async () => {
return await getAllInitialInfo();
// export const load: PageServerLoad = async ({ params }) => {
// return {
// data: await getPlayerInfo(parseInt(params.playerId))
// };
// };
export const load: PageServerLoad = async ({ params }) => {
return {
playerInfo: getPlayerInfo(parseInt(params.playerId))
};
};

View File

@@ -1,20 +1,75 @@
<script lang="ts">
import type { PageProps } from './$types';
import { getPlayerData } from './data.remote';
let { data, params }: PageProps = $props();
let { data }: PageProps = $props();
console.log(data);
let some = await getPlayerData(params.playerId);
console.log(some)
let playerId = params.playerId;
console.log(parseInt(playerId));
function marquee(node: HTMLElement) {
function measure() {
const inner = node.querySelector<HTMLElement>('.marquee-inner');
if (!inner) return;
const overflow = inner.scrollWidth - node.clientWidth;
if (overflow > 2) {
node.style.setProperty('--scroll-dist', `-${overflow + 6}px`);
inner.classList.add('scrolling');
} else {
inner.classList.remove('scrolling');
}
}
measure();
const ro = new ResizeObserver(measure);
ro.observe(node);
return { destroy: () => ro.disconnect() };
}
</script>
<div class="my-3 flex flex-col justify-center">
<div class="player-box w-full" style="--c:red">
<!-- <span>{console.log(getPlayerInfo(playerId))}</span> -->
{#await data.playerInfo}
<div>Loading...</div>
{:then playerInfo}
{console.log(playerInfo)}
<div class="mt-5 flex justify-center">
<div
class="player-single-box aspect-square w-full max-w-[95vw] justify-center md:aspect-2/1 lg:aspect-2/1"
style="--c:{playerInfo.teamInfo.color}"
>
<div class="player-ghost" aria-hidden="true">
<svg viewBox="0 0.1 100 0.6" preserveAspectRatio="none" class="ghost-svg">
<text
x="0"
y="0.7"
font-size="1"
dominant-baseline="auto"
textLength="100"
lengthAdjust="spacingAndGlyphs"
font-family="'Black Ops One',system-ui">{playerInfo.firstName}</text
>
</svg>
</div>
<div class=" goldman player-name-wrap text-5xl" use:marquee>
{playerInfo.firstName}
{playerInfo.lastName}
</div>
</div>
</div>
</div>
{/await}
<style>
.player-single-box {
flex: 1 1 0;
position: relative;
overflow: hidden;
border-radius: 8px;
border: 1.5px solid var(--c);
color: var(--c);
background: color-mix(in srgb, var(--c) 10%, transparent);
padding: 5px 7px 5px;
display: flex;
flex-direction: column;
}
.player-name-wrap {
position: relative;
z-index: 1;
text-decoration: none;
overflow: hidden;
white-space: nowrap;
}
</style>

View File

@@ -1,8 +0,0 @@
import { query } from '$app/server';
import { getPlayerInfo } from '$lib/server/databaseManager';
export const getPlayerData = query(async (playerId: number) => {
const playerInfo = await getPlayerInfo(playerId);
return playerInfo;
});