added inserting players
This commit is contained in:
@@ -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 { globalEmitter } from '$lib/server/globalEmitter';
|
||||||
import { generateEndpoint } from '$lib/server/endpoint';
|
|
||||||
|
|
||||||
export async function POST({ request }: any) {
|
export async function POST({ request, locals }: any) {
|
||||||
return new Response('ok');
|
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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,18 +67,23 @@ export const load: PageServerLoad = async ({ locals }) => {
|
|||||||
.innerJoin(players, eq(registeredPlayers.playerID, players.id))
|
.innerJoin(players, eq(registeredPlayers.playerID, players.id))
|
||||||
.innerJoin(teams, eq(players.team, teams.id))
|
.innerJoin(teams, eq(players.team, teams.id))
|
||||||
.innerJoin(divisions, eq(registeredEvents.division, divisions.id))
|
.innerJoin(divisions, eq(registeredEvents.division, divisions.id))
|
||||||
.orderBy(registeredPlayers.id);
|
.orderBy(teams.name, registeredEvents.id, brackets.name);
|
||||||
|
|
||||||
const allPlayers = await db
|
const allPlayers = await db
|
||||||
.select({
|
.select({
|
||||||
id: players.id,
|
id: players.id,
|
||||||
firstName: players.firstName,
|
firstName: players.firstName,
|
||||||
lastName: players.lastName,
|
lastName: players.lastName,
|
||||||
teamId: players.team
|
teamId: players.team,
|
||||||
|
teamName: teams.name,
|
||||||
|
teamColor: teams.color
|
||||||
})
|
})
|
||||||
.from(players)
|
.from(players)
|
||||||
|
.innerJoin(teams, eq(players.team, teams.id))
|
||||||
.orderBy(players.firstName);
|
.orderBy(players.firstName);
|
||||||
|
|
||||||
|
const bracketsList = await db.select().from(brackets).orderBy(brackets.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
events: events.map((e) => ({ id: e.id, name: e.name, division: e.division, state: e.state })),
|
events: events.map((e) => ({ id: e.id, name: e.name, division: e.division, state: e.state })),
|
||||||
registrations: registrations.map((r) => ({
|
registrations: registrations.map((r) => ({
|
||||||
@@ -99,6 +104,7 @@ export const load: PageServerLoad = async ({ locals }) => {
|
|||||||
points: r.points,
|
points: r.points,
|
||||||
stateLabel: r.state === 0 ? 'Not started' : r.state === 1 ? 'Ongoing' : 'Finished'
|
stateLabel: r.state === 0 ? 'Not started' : r.state === 1 ? 'Ongoing' : 'Finished'
|
||||||
})),
|
})),
|
||||||
allPlayers
|
allPlayers,
|
||||||
|
brackets: bracketsList
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,12 +6,20 @@
|
|||||||
|
|
||||||
type Registration = (typeof data.registrations)[number];
|
type Registration = (typeof data.registrations)[number];
|
||||||
type PlayerInfo = (typeof data.allPlayers)[number];
|
type PlayerInfo = (typeof data.allPlayers)[number];
|
||||||
|
type Bracket = (typeof data.brackets)[number];
|
||||||
|
|
||||||
let selectedEventId = $state<number | ''>('');
|
let selectedEventId = $state<number | ''>('');
|
||||||
let searchQuery = $state('');
|
let searchQuery = $state('');
|
||||||
let editingId = $state<number | null>(null);
|
let editingId = $state<number | null>(null);
|
||||||
let editPlayerId = $state<number>(0);
|
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(
|
let filteredRegistrations = $derived(
|
||||||
data.registrations.filter((r) => {
|
data.registrations.filter((r) => {
|
||||||
if (selectedEventId !== '' && r.eventId !== selectedEventId) return false;
|
if (selectedEventId !== '' && r.eventId !== selectedEventId) return false;
|
||||||
@@ -63,6 +71,52 @@
|
|||||||
if (idx !== -1) data.registrations.splice(idx, 1);
|
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 {
|
function stateColor(state: number): string {
|
||||||
if (state === 0) return 'var(--ctp-mocha-yellow)';
|
if (state === 0) return 'var(--ctp-mocha-yellow)';
|
||||||
if (state === 1) return 'var(--ctp-mocha-green)';
|
if (state === 1) return 'var(--ctp-mocha-green)';
|
||||||
@@ -103,8 +157,51 @@
|
|||||||
>
|
>
|
||||||
<span>Registrations</span>
|
<span>Registrations</span>
|
||||||
<span class="text-sm opacity-60">{filteredRegistrations.length} entries</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>
|
</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">
|
<Table data={filteredRegistrations} maxHeight="none">
|
||||||
{#snippet header()}
|
{#snippet header()}
|
||||||
<th class="px-2">RP ID</th>
|
<th class="px-2">RP ID</th>
|
||||||
@@ -174,6 +271,37 @@
|
|||||||
color: var(--ctp-mocha-red, #f38ba8);
|
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 {
|
td select {
|
||||||
padding: 0.15rem 0.25rem;
|
padding: 0.15rem 0.25rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
@@ -184,3 +312,4 @@
|
|||||||
max-width: 140px;
|
max-width: 140px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user