added inserting players

This commit is contained in:
2026-07-07 20:55:18 +01:00
parent df8f5bc9a9
commit 097ff77c8a
3 changed files with 190 additions and 7 deletions

View File

@@ -1,7 +1,55 @@
import { getAllRegisteredEventPlayers } from '$lib/server/databaseManager';
import { error, json } from '@sveltejs/kit';
import { eq, and } from 'drizzle-orm';
import { db } from '$lib/server/db';
import { registeredPlayers, registeredEvents, scorers } from '$lib/server/db/schema';
import { globalEmitter } from '$lib/server/globalEmitter';
import { generateEndpoint } from '$lib/server/endpoint';
export async function POST({ request }: any) {
return new Response('ok');
export async function POST({ request, locals }: any) {
if (!locals.user) throw error(401, 'Unauthorized');
const [row] = await db.select({ role: scorers.role }).from(scorers).where(eq(scorers.id, locals.user.id));
if (row?.role !== 'admin') throw error(403, 'Forbidden');
const body = await request.json();
const { eventId, playerId, bracketId } = body;
if (typeof eventId !== 'number' || typeof playerId !== 'number' || typeof bracketId !== 'number') {
throw error(400, 'eventId, playerId, and bracketId are required');
}
const event = await db
.select({ state: registeredEvents.state })
.from(registeredEvents)
.where(eq(registeredEvents.id, eventId))
.get();
if (!event) throw error(404, 'Event not found');
if (event.state !== 0) throw error(403, 'Cannot register players for events that are ongoing or finished');
const existing = await db
.select({ id: registeredPlayers.id })
.from(registeredPlayers)
.where(
and(
eq(registeredPlayers.playerID, playerId),
eq(registeredPlayers.registeredEventID, eventId)
)
)
.get();
if (existing) {
throw error(409, 'Player is already registered for this event');
}
const [inserted] = await db
.insert(registeredPlayers)
.values({
playerID: playerId,
registeredEventID: eventId,
bracket: bracketId
})
.returning({ id: registeredPlayers.id });
globalEmitter.emit('eventUpdate');
return json({ id: inserted.id });
}

View File

@@ -67,18 +67,23 @@ export const load: PageServerLoad = async ({ locals }) => {
.innerJoin(players, eq(registeredPlayers.playerID, players.id))
.innerJoin(teams, eq(players.team, teams.id))
.innerJoin(divisions, eq(registeredEvents.division, divisions.id))
.orderBy(registeredPlayers.id);
.orderBy(teams.name, registeredEvents.id, brackets.name);
const allPlayers = await db
.select({
id: players.id,
firstName: players.firstName,
lastName: players.lastName,
teamId: players.team
teamId: players.team,
teamName: teams.name,
teamColor: teams.color
})
.from(players)
.innerJoin(teams, eq(players.team, teams.id))
.orderBy(players.firstName);
const bracketsList = await db.select().from(brackets).orderBy(brackets.id);
return {
events: events.map((e) => ({ id: e.id, name: e.name, division: e.division, state: e.state })),
registrations: registrations.map((r) => ({
@@ -99,6 +104,7 @@ export const load: PageServerLoad = async ({ locals }) => {
points: r.points,
stateLabel: r.state === 0 ? 'Not started' : r.state === 1 ? 'Ongoing' : 'Finished'
})),
allPlayers
allPlayers,
brackets: bracketsList
};
};

View File

@@ -6,12 +6,20 @@
type Registration = (typeof data.registrations)[number];
type PlayerInfo = (typeof data.allPlayers)[number];
type Bracket = (typeof data.brackets)[number];
let selectedEventId = $state<number | ''>('');
let searchQuery = $state('');
let editingId = $state<number | null>(null);
let editPlayerId = $state<number>(0);
let showNewForm = $state(false);
let newEventId = $state<number>(0);
let newBracketId = $state<number>(0);
let newPlayerId = $state<number>(0);
let pendingEvents = $derived(data.events.filter((e) => e.state === 0));
let filteredRegistrations = $derived(
data.registrations.filter((r) => {
if (selectedEventId !== '' && r.eventId !== selectedEventId) return false;
@@ -63,6 +71,52 @@
if (idx !== -1) data.registrations.splice(idx, 1);
}
function resetNewForm() {
showNewForm = false;
newEventId = 0;
newBracketId = 0;
newPlayerId = 0;
}
async function submitNew() {
if (!newEventId || !newBracketId || !newPlayerId) return;
const res = await fetch('/api/registeredPlayers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ eventId: newEventId, playerId: newPlayerId, bracketId: newBracketId })
});
if (!res.ok) {
const err = await res.json();
alert(err.message || 'Failed to create registration');
return;
}
const result = await res.json();
const selectedPlayer = data.allPlayers.find((p) => p.id === newPlayerId);
const selectedEvent = data.events.find((e) => e.id === newEventId);
const selectedBracket = data.brackets.find((b) => b.id === newBracketId);
if (!selectedPlayer || !selectedEvent || !selectedBracket) return;
const newReg: Registration = {
id: result.id,
registeredPlayerId: result.id,
playerId: newPlayerId,
firstName: selectedPlayer.firstName,
lastName: selectedPlayer.lastName,
placement: 0,
bracket: selectedBracket.name,
eventId: newEventId,
eventName: selectedEvent.name,
teamId: selectedPlayer.teamId,
teamName: selectedPlayer.teamName,
teamColor: selectedPlayer.teamColor,
state: 0,
division: selectedEvent.division,
points: 0,
stateLabel: 'Not started'
};
data.registrations.unshift(newReg);
resetNewForm();
}
function stateColor(state: number): string {
if (state === 0) return 'var(--ctp-mocha-yellow)';
if (state === 1) return 'var(--ctp-mocha-green)';
@@ -103,8 +157,51 @@
>
<span>Registrations</span>
<span class="text-sm opacity-60">{filteredRegistrations.length} entries</span>
<button class="action-btn new-btn" onclick={() => { showNewForm = !showNewForm; }}>
{showNewForm ? 'Cancel' : 'New'}
</button>
</div>
{#if showNewForm}
<div class="card new-form-card">
<div class="new-form-title">New Registration</div>
<div class="flex flex-wrap gap-4 items-end">
<div>
<label class="block text-sm mb-1" for="new-event">Event</label>
<select id="new-event" bind:value={newEventId}>
<option value={0}>Select event...</option>
{#each pendingEvents as event (event.id)}
<option value={event.id}>{event.name} {event.division}</option>
{/each}
</select>
</div>
<div>
<label class="block text-sm mb-1" for="new-bracket">Bracket</label>
<select id="new-bracket" bind:value={newBracketId}>
<option value={0}>Select bracket...</option>
{#each data.brackets as bracket (bracket.id)}
<option value={bracket.id}>{bracket.name}</option>
{/each}
</select>
</div>
<div>
<label class="block text-sm mb-1" for="new-player">Player</label>
<select id="new-player" bind:value={newPlayerId}>
<option value={0}>Select player...</option>
{#each data.allPlayers as player (player.id)}
<option value={player.id}>{player.firstName} {player.lastName} ({player.teamName})</option>
{/each}
</select>
</div>
<div>
<button class="action-btn submit-btn" onclick={submitNew} disabled={!newEventId || !newBracketId || !newPlayerId}>
Insert
</button>
</div>
</div>
</div>
{/if}
<Table data={filteredRegistrations} maxHeight="none">
{#snippet header()}
<th class="px-2">RP ID</th>
@@ -174,6 +271,37 @@
color: var(--ctp-mocha-red, #f38ba8);
}
.action-btn.new-btn {
border-color: var(--ctp-mocha-green, #a6e3a1);
color: var(--ctp-mocha-green, #a6e3a1);
font-size: 0.85rem;
padding: 0.25rem 0.75rem;
}
.action-btn.submit-btn {
border-color: var(--ctp-mocha-blue, #89b4fa);
color: var(--ctp-mocha-blue, #89b4fa);
padding: 0.3rem 0.75rem;
font-size: 0.85rem;
}
.action-btn.submit-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.new-form-card {
margin-bottom: 1rem;
padding: 0.75rem 1rem;
}
.new-form-title {
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 0.5rem;
opacity: 0.8;
}
td select {
padding: 0.15rem 0.25rem;
font-size: 0.75rem;
@@ -184,3 +312,4 @@
max-width: 140px;
}
</style>