added ledger view (thanks opencode)
This commit is contained in:
@@ -1 +1,134 @@
|
||||
<div>some</div>
|
||||
<script lang="ts">
|
||||
import Table from '$lib/ui/Table.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
type LedgerEntry = {
|
||||
id: number;
|
||||
type: string;
|
||||
eventName: string;
|
||||
timestamp: string;
|
||||
teamPoints: Record<string, number>;
|
||||
};
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let entries = $state<LedgerEntry[]>([]);
|
||||
let showAll = $state(false);
|
||||
let selectedEventId = $state<number | ''>('');
|
||||
let inputPoints = $state<Record<string, string>>({});
|
||||
let submitting = $state(false);
|
||||
|
||||
let teamNames = $derived(data.teams.map((t) => t.name).sort());
|
||||
|
||||
function formatTime(ts: string) {
|
||||
return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
|
||||
}
|
||||
|
||||
function formatPoints(n: number) {
|
||||
return n >= 0 ? `+${n}` : `${n}`;
|
||||
}
|
||||
|
||||
async function loadEntries(all: boolean = false) {
|
||||
const res = await fetch(`/api/ledger?limit=${all ? 'all' : '100'}`);
|
||||
entries = await res.json();
|
||||
showAll = all;
|
||||
}
|
||||
|
||||
async function submitEntry() {
|
||||
const event = data.events.find((e) => e.id === selectedEventId);
|
||||
if (!event) return;
|
||||
|
||||
submitting = true;
|
||||
try {
|
||||
const teamPoints: Record<string, number> = {};
|
||||
for (const [team, val] of Object.entries(inputPoints)) {
|
||||
const num = parseInt(val);
|
||||
if (!isNaN(num) && num !== 0) {
|
||||
teamPoints[team] = num;
|
||||
}
|
||||
}
|
||||
|
||||
await fetch('/api/ledger', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ eventId: event.id, teamPoints })
|
||||
});
|
||||
|
||||
selectedEventId = '';
|
||||
inputPoints = {};
|
||||
await loadEntries(showAll);
|
||||
} finally {
|
||||
submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
loadEntries();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="p-4">
|
||||
<h2 class="mb-4 text-lg font-bold">Manual Ledger Entry</h2>
|
||||
<div class="mb-4 flex flex-wrap gap-4 items-end">
|
||||
<div>
|
||||
<label class="block text-sm mb-1">Event</label>
|
||||
<select bind:value={selectedEventId} class="border rounded px-2 py-1">
|
||||
<option value="">Select event...</option>
|
||||
{#each data.events as event (event.id)}
|
||||
<option value={event.id}>{event.name} — {event.division}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
{#each teamNames as team}
|
||||
<div>
|
||||
<label class="block text-sm mb-1">{team}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={inputPoints[team] ?? '0'}
|
||||
oninput={(e) => { inputPoints[team] = (e.target as HTMLInputElement).value; }}
|
||||
class="border rounded px-2 py-1 w-20"
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
<button
|
||||
onclick={submitEntry}
|
||||
disabled={!selectedEventId || submitting}
|
||||
class="border rounded px-4 py-1 bg-green-700 text-white disabled:opacity-50"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Table data={entries} maxHeight="none">
|
||||
{#snippet header()}
|
||||
<th class="px-2">ID</th>
|
||||
<th class="px-2">Type</th>
|
||||
<th class="px-2">Event</th>
|
||||
{#each teamNames as team}
|
||||
<th class="px-2">{team}</th>
|
||||
{/each}
|
||||
<th class="px-2">Time</th>
|
||||
{/snippet}
|
||||
{#snippet row(entry: LedgerEntry)}
|
||||
<td class="px-2">{entry.id}</td>
|
||||
<td class="px-2">{entry.type}</td>
|
||||
<td class="px-2">{entry.eventName}</td>
|
||||
{#each teamNames as team}
|
||||
<td class="px-2">
|
||||
{entry.teamPoints[team] !== undefined ? formatPoints(entry.teamPoints[team]) : ''}
|
||||
</td>
|
||||
{/each}
|
||||
<td class="px-2">{formatTime(entry.timestamp)}</td>
|
||||
{/snippet}
|
||||
</Table>
|
||||
|
||||
<div class="mt-4">
|
||||
<button
|
||||
onclick={() => loadEntries(!showAll)}
|
||||
class="border rounded px-4 py-1"
|
||||
>
|
||||
{showAll ? 'Show 100 most recent' : 'Load all entries'}
|
||||
</button>
|
||||
<span class="ml-2 text-sm">{entries.length} entries shown</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user