(add): Reports and some corrections.

This commit is contained in:
2026-06-27 17:55:29 -04:00
parent 37bba94880
commit 9602ed52a6
17 changed files with 529 additions and 228 deletions
+14
View File
@@ -13,6 +13,8 @@ export const bS = {
green: 'px-2 py-1 bg-green-300 border border-gray-700 rounded hover:bg-green-400 cursor-pointer', green: 'px-2 py-1 bg-green-300 border border-gray-700 rounded hover:bg-green-400 cursor-pointer',
orange: orange:
'px-2 py-1 bg-orange-300 border border-gray-700 rounded hover:bg-orange-400 cursor-pointer', 'px-2 py-1 bg-orange-300 border border-gray-700 rounded hover:bg-orange-400 cursor-pointer',
purple:
'px-2 py-1 bg-purple-300 border border-gray-700 rounded hover:bg-purple-400 cursor-pointer',
red: 'px-2 py-1 bg-red-300 border border-gray-700 rounded hover:bg-red-400 cursor-pointer' red: 'px-2 py-1 bg-red-300 border border-gray-700 rounded hover:bg-red-400 cursor-pointer'
}; };
@@ -26,9 +28,21 @@ export const bAS = {
'px-2 py-1 bg-green-300 border-4 border-gray-700 rounded hover:bg-green-400 cursor-pointer', 'px-2 py-1 bg-green-300 border-4 border-gray-700 rounded hover:bg-green-400 cursor-pointer',
orange: orange:
'px-2 py-1 bg-orange-300 border-4 border-gray-700 rounded hover:bg-orange-400 cursor-pointer', 'px-2 py-1 bg-orange-300 border-4 border-gray-700 rounded hover:bg-orange-400 cursor-pointer',
purple:
'px-2 py-1 bg-purple-300 border-4 border-gray-700 rounded hover:bg-purple-400 cursor-pointer',
red: 'px-2 py-1 bg-red-300 border-4 border-gray-700 rounded hover:bg-red-400 cursor-pointer' red: 'px-2 py-1 bg-red-300 border-4 border-gray-700 rounded hover:bg-red-400 cursor-pointer'
}; };
export const iS = { export const iS = {
normal: 'p-1 border border-black' normal: 'p-1 border border-black'
}; };
export const rBS = {
white: 'even:bg-gray-100',
blue: 'even:bg-blue-100',
yellow: 'even:bg-yellow-100',
green: 'even:bg-green-100',
orange: 'even:bg-orange-100',
purple: 'even:bg-purple-100',
red: 'even:bg-red-100'
};
+9 -6
View File
@@ -40,9 +40,10 @@ export const drawing = sqliteView('drawing', {
last_name: text('last_name'), last_name: text('last_name'),
first_name: text('first_name'), first_name: text('first_name'),
phone_number: text('phone_number') phone_number: text('phone_number')
}).as(sql`SELECT b.prefix, b.b_id, b.description, b.winning_ticket, t.last_name, t.first_name, t.phone_number })
.as(sql`SELECT b.prefix, b.b_id, b.description, b.winning_ticket, t.last_name, t.first_name, t.phone_number
FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id
ORDER BY b.prefix, b.b_id`) ORDER BY b.prefix, b.b_id`);
export const reportByName = sqliteView('report_by_name', { export const reportByName = sqliteView('report_by_name', {
last_name: text('last_name'), last_name: text('last_name'),
@@ -54,9 +55,10 @@ export const reportByName = sqliteView('report_by_name', {
description: text('description'), description: text('description'),
donors: text('donors'), donors: text('donors'),
winning_ticket: integer('winning_ticket') winning_ticket: integer('winning_ticket')
}).as(sql`SELECT t.last_name, t.first_name, t.phone_number, t.pref, b.prefix, b.b_id, b.description, b.donors, b.winning_ticket })
.as(sql`SELECT t.last_name, t.first_name, t.phone_number, t.pref, b.prefix, b.b_id, b.description, b.donors, b.winning_ticket
FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id
ORDER BY t.last_name, t.first_name, t.phone_number, b.prefix, b.b_id`) ORDER BY t.last_name, t.first_name, t.phone_number, b.prefix, b.b_id`);
export const reportByBasket = sqliteView('report_by_basket', { export const reportByBasket = sqliteView('report_by_basket', {
prefix: text('prefix'), prefix: text('prefix'),
@@ -68,6 +70,7 @@ export const reportByBasket = sqliteView('report_by_basket', {
first_name: text('first_name'), first_name: text('first_name'),
phone_number: text('phone_number'), phone_number: text('phone_number'),
pref: text('pref') pref: text('pref')
}).as(sql`SELECT b.prefix, b.b_id, b.description, b.donors, b.winning_ticket, t.last_name, t.first_name, t.phone_number, t.pref })
.as(sql`SELECT b.prefix, b.b_id, b.description, b.donors, b.winning_ticket, t.last_name, t.first_name, t.phone_number, t.pref
FROM baskets b LEFT JOIN tickets t on b.prefix = t.prefix AND b.winning_ticket = t.t_id FROM baskets b LEFT JOIN tickets t on b.prefix = t.prefix AND b.winning_ticket = t.t_id
ORDER BY b.prefix, b.b_id`) ORDER BY b.prefix, b.b_id`);
+17 -1
View File
@@ -59,6 +59,8 @@
class={curPrefix == prefix.prefix ? bAS[prefix.color] : bS[prefix.color]} class={curPrefix == prefix.prefix ? bAS[prefix.color] : bS[prefix.color]}
onclick={() => (curPrefix = prefix.prefix)}>{prefix.prefix}</button onclick={() => (curPrefix = prefix.prefix)}>{prefix.prefix}</button
> >
{:else}
<div>No Prefixes</div>
{/each} {/each}
</div> </div>
{#if curPrefix} {#if curPrefix}
@@ -71,7 +73,21 @@
<a href={resolve('/baskets/[prefix]', { prefix: curPrefix })} class={bS[pColor]} <a href={resolve('/baskets/[prefix]', { prefix: curPrefix })} class={bS[pColor]}
>Baskets</a >Baskets</a
> >
<a href={resolve('/drawing/[prefix]', {prefix: curPrefix})} class="{bS[pColor]} col-span-2">Drawing Form</a> <a
href={resolve('/drawing/[prefix]', { prefix: curPrefix })}
class="{bS[pColor]} col-span-2">Drawing Form</a
>
</div>
</div>
<div class="flex flex-col gap-1 items-center border border-black rounded">
<h2 class="text-lg font-bold">Reports:</h2>
<div class="grid grid-cols-2 gap-1 p-1 text-center">
<a href={resolve('/reports/byname/[prefix]', { prefix: curPrefix })} class={bS[pColor]}
>Winners By Name</a
>
<a href={resolve('/reports/bybasket/[prefix]', { prefix: curPrefix })} class={bS[pColor]}
>Winners By Basket</a
>
</div> </div>
</div> </div>
{:else} {:else}
+14 -8
View File
@@ -1,8 +1,8 @@
import { db } from "$lib/server/db"; import { db } from '$lib/server/db';
import { baskets, drawing } from "$lib/server/db/schema"; import { baskets, drawing } from '$lib/server/db/schema';
import { getPath, getSettings } from "$lib/server/settings" import { getPath, getSettings } from '$lib/server/settings';
import { error, json } from "@sveltejs/kit"; import { error, json } from '@sveltejs/kit';
import { sql } from "drizzle-orm"; import { sql } from 'drizzle-orm';
export const GET = async () => { export const GET = async () => {
const s = getSettings(); const s = getSettings();
@@ -20,7 +20,7 @@ export const GET = async () => {
const data = await db.select().from(drawing).orderBy(drawing.prefix, drawing.b_id); const data = await db.select().from(drawing).orderBy(drawing.prefix, drawing.b_id);
return json(data); return json(data);
} }
} };
export const POST = async ({ request }) => { export const POST = async ({ request }) => {
const reqData = await request.json(); const reqData = await request.json();
@@ -34,6 +34,12 @@ export const POST = async ({ request }) => {
}); });
if (!res.ok) throw error(res.status); if (!res.ok) throw error(res.status);
} }
await db.insert(baskets).values(reqData).onConflictDoUpdate({target: [baskets.prefix, baskets.b_id], set: {winning_ticket: sql`EXCLUDED.winning_ticket`}}); await db
.insert(baskets)
.values(reqData)
.onConflictDoUpdate({
target: [baskets.prefix, baskets.b_id],
set: { winning_ticket: sql`EXCLUDED.winning_ticket` }
});
return json(reqData); return json(reqData);
} };
@@ -10,7 +10,9 @@ export const GET = async ({ params }) => {
if (s.remote_server) { if (s.remote_server) {
const connStr = getPath(s); const connStr = getPath(s);
try { try {
const res = await fetch(`${connStr}/api/drawing/${prefix}`, {headers: {'TAM-KEY': s.remote_key}}); const res = await fetch(`${connStr}/api/drawing/${prefix}`, {
headers: { 'TAM-KEY': s.remote_key }
});
if (!res.ok) throw error(res.status); if (!res.ok) throw error(res.status);
const data = await res.json(); const data = await res.json();
return json(data); return json(data);
@@ -18,7 +20,11 @@ export const GET = async ({ params }) => {
return json([]); return json([]);
} }
} else { } else {
const data = await db.select().from(drawing).where(eq(drawing.prefix, prefix)).orderBy(drawing.prefix, drawing.b_id); const data = await db
.select()
.from(drawing)
.where(eq(drawing.prefix, prefix))
.orderBy(drawing.prefix, drawing.b_id);
return json(data); return json(data);
} }
} };
@@ -8,13 +8,13 @@ const placeholder_data = (prefix, b_id) => {
return { return {
prefix, prefix,
b_id, b_id,
description: "", description: '',
winning_ticket: 0, winning_ticket: 0,
last_name: "", last_name: '',
first_name: "", first_name: '',
phone_number: "" phone_number: ''
} };
} };
export const GET = async ({ params }) => { export const GET = async ({ params }) => {
const [prefix, b_id] = [params.prefix, parseInt(params.b_id)]; const [prefix, b_id] = [params.prefix, parseInt(params.b_id)];
@@ -22,23 +22,29 @@ export const GET = async ({ params }) => {
if (s.remote_server) { if (s.remote_server) {
const connStr = getPath(s); const connStr = getPath(s);
try { try {
const res = await fetch(`${connStr}/api/drawing/${prefix}/${b_id}`, {headers: {'TAM-KEY': s.remote_key}}); const res = await fetch(`${connStr}/api/drawing/${prefix}/${b_id}`, {
headers: { 'TAM-KEY': s.remote_key }
});
if (!res.ok) throw error(res.status); if (!res.ok) throw error(res.status);
const [data] = await res.json(); const [data] = await res.json();
if (data) { if (data) {
return json(data); return json(data);
} else { } else {
return json(placeholder_data(prefix, b_id)); return json(placeholder_data(prefix, b_id));
}; }
} catch { } catch {
return json(placeholder_data(prefix, b_id)); return json(placeholder_data(prefix, b_id));
} }
} else { } else {
const [data] = await db.select().from(drawing).where(and(eq(drawing.prefix, prefix), eq(drawing.b_id, b_id))).orderBy(drawing.prefix, drawing.b_id); const [data] = await db
.select()
.from(drawing)
.where(and(eq(drawing.prefix, prefix), eq(drawing.b_id, b_id)))
.orderBy(drawing.prefix, drawing.b_id);
if (data) { if (data) {
return json(data); return json(data);
} else { } else {
return json(placeholder_data(prefix, b_id)); return json(placeholder_data(prefix, b_id));
} }
} }
} };
@@ -8,34 +8,44 @@ const placeholder_data = (prefix, b_id) => {
return { return {
prefix, prefix,
b_id, b_id,
description: "", description: '',
winning_ticket: 0, winning_ticket: 0,
last_name: "", last_name: '',
first_name: "", first_name: '',
phone_number: "" phone_number: ''
}; };
}; };
export const GET = async ({ params }) => { export const GET = async ({ params }) => {
const [prefix, id_from, id_to] = [params.prefix, parseInt(params.id_from), parseInt(params.id_to)]; const [prefix, id_from, id_to] = [
params.prefix,
parseInt(params.id_from),
parseInt(params.id_to)
];
const s = getSettings(); const s = getSettings();
const rtnData = {}; const rtnData = {};
for (let i = id_from; i <= id_to; i++) { for (let i = id_from; i <= id_to; i++) {
rtnData[i] = { ...placeholder_data(prefix, i) }; rtnData[i] = { ...placeholder_data(prefix, i) };
}; }
if (s.remote_server) { if (s.remote_server) {
const connStr = getPath(s); const connStr = getPath(s);
try { try {
const res = await fetch(`${connStr}/api/drawing/${prefix}/${id_from}/${id_to}`, {headers: {'TAM-KEY': s.remote_key}}); const res = await fetch(`${connStr}/api/drawing/${prefix}/${id_from}/${id_to}`, {
headers: { 'TAM-KEY': s.remote_key }
});
if (!res.ok) throw error(res.status); if (!res.ok) throw error(res.status);
const data = Array.from(await res.json()); const data = Array.from(await res.json());
data.forEach((b) => rtnData[b.b_id] = b); data.forEach((b) => (rtnData[b.b_id] = b));
} catch { } catch {
return json([]); return json([]);
} }
} else { } else {
const data = await db.select().from(drawing).where(and(eq(drawing.prefix, prefix), between(drawing.b_id, id_from, id_to))).orderBy(drawing.prefix, drawing.b_id); const data = await db
data.forEach((b) => rtnData[b.b_id] = b); .select()
}; .from(drawing)
return json(Object.values(rtnData)); .where(and(eq(drawing.prefix, prefix), between(drawing.b_id, id_from, id_to)))
.orderBy(drawing.prefix, drawing.b_id);
data.forEach((b) => (rtnData[b.b_id] = b));
} }
return json(Object.values(rtnData));
};
@@ -10,7 +10,9 @@ export const GET = async ({ params }) => {
if (s.remote_server) { if (s.remote_server) {
const connStr = getPath(s); const connStr = getPath(s);
try { try {
const res = await fetch(`${connStr}/api/reports/bybasket/${prefix}`, {headers: {'TAM-KEY': s.remote_key}}); const res = await fetch(`${connStr}/api/reports/bybasket/${prefix}`, {
headers: { 'TAM-KEY': s.remote_key }
});
if (!res.ok) throw error(res.status); if (!res.ok) throw error(res.status);
const data = await res.json(); const data = await res.json();
return json(data); return json(data);
@@ -21,4 +23,4 @@ export const GET = async ({ params }) => {
const data = await db.select().from(reportByBasket).where(eq(reportByBasket.prefix, prefix)); const data = await db.select().from(reportByBasket).where(eq(reportByBasket.prefix, prefix));
return json(data); return json(data);
} }
} };
@@ -10,7 +10,9 @@ export const GET = async ({ params }) => {
if (s.remote_server) { if (s.remote_server) {
const connStr = getPath(s); const connStr = getPath(s);
try { try {
const res = await fetch(`${connStr}/api/reports/byname/${prefix}`, {headers: {'TAM-KEY': s.remote_key}}); const res = await fetch(`${connStr}/api/reports/byname/${prefix}`, {
headers: { 'TAM-KEY': s.remote_key }
});
if (!res.ok) throw error(res.status); if (!res.ok) throw error(res.status);
const data = await res.json(); const data = await res.json();
return json(data); return json(data);
@@ -21,4 +23,4 @@ export const GET = async ({ params }) => {
const data = await db.select().from(reportByName).where(eq(reportByName.prefix, prefix)); const data = await db.select().from(reportByName).where(eq(reportByName.prefix, prefix));
return json(data); return json(data);
} }
} };
@@ -2,7 +2,7 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { afterNavigate, beforeNavigate } from '$app/navigation'; import { afterNavigate, beforeNavigate } from '$app/navigation';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { bS, bAS, iS } from '$lib/client/styles'; import { bS, bAS, iS, rBS } from '$lib/client/styles';
import HeaderBar from '$lib/client/components/HeaderBar.svelte'; import HeaderBar from '$lib/client/components/HeaderBar.svelte';
import PagerBar from '$lib/client/components/PagerBar.svelte'; import PagerBar from '$lib/client/components/PagerBar.svelte';
import CommandBar from '$lib/client/components/CommandBar.svelte'; import CommandBar from '$lib/client/components/CommandBar.svelte';
@@ -28,7 +28,7 @@
let pager = $state({ idFrom: 0, idTo: 0 }); let pager = $state({ idFrom: 0, idTo: 0 });
let items = $state([]); let items = $state([]);
let itemsLength = $derived(items.length); let itemsLength = $derived.by(() => (items.length ? items.length : 0));
let itemsBuffer = $derived(items.filter((i) => i.changed)); let itemsBuffer = $derived(items.filter((i) => i.changed));
const functions = { const functions = {
getPage: async () => { getPage: async () => {
@@ -181,7 +181,7 @@
<tbody> <tbody>
{#each items as item, idx (item.b_id)} {#each items as item, idx (item.b_id)}
<tr <tr
class="focus-within:font-bold" class="focus-within:font-bold {rBS[prefix.color]}"
onfocusin={(e) => { onfocusin={(e) => {
changeIdx(idx); changeIdx(idx);
e.target.scrollIntoView({ block: 'center' }); e.target.scrollIntoView({ block: 'center' });
@@ -2,7 +2,7 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { afterNavigate, beforeNavigate } from '$app/navigation'; import { afterNavigate, beforeNavigate } from '$app/navigation';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { bS, bAS, iS } from '$lib/client/styles'; import { bS, bAS, iS, rBS } from '$lib/client/styles';
import HeaderBar from '$lib/client/components/HeaderBar.svelte'; import HeaderBar from '$lib/client/components/HeaderBar.svelte';
import PagerBar from '$lib/client/components/PagerBar.svelte'; import PagerBar from '$lib/client/components/PagerBar.svelte';
import CommandBar from '$lib/client/components/CommandBar.svelte'; import CommandBar from '$lib/client/components/CommandBar.svelte';
@@ -28,7 +28,7 @@
let pager = $state({ idFrom: 0, idTo: 0 }); let pager = $state({ idFrom: 0, idTo: 0 });
let items = $state([]); let items = $state([]);
let itemsLength = $derived(items.length); let itemsLength = $derived.by(() => (items.length ? items.length : 0));
let itemsBuffer = $derived(items.filter((i) => i.changed)); let itemsBuffer = $derived(items.filter((i) => i.changed));
const functions = { const functions = {
getPage: async () => { getPage: async () => {
@@ -159,7 +159,7 @@
<tr> <tr>
<td colspan="50"> <td colspan="50">
<HeaderBar> <HeaderBar>
<div>Baskets:</div> <div>Drawing Forms:</div>
{#each prefixes as p (p.prefix)} {#each prefixes as p (p.prefix)}
<a <a
href={resolve('/drawing/[prefix]', { prefix: p.prefix })} href={resolve('/drawing/[prefix]', { prefix: p.prefix })}
@@ -181,16 +181,14 @@
<tbody> <tbody>
{#each items as item, idx (item.b_id)} {#each items as item, idx (item.b_id)}
<tr <tr
class="focus-within:font-bold" class="focus-within:font-bold {rBS[prefix.color]}"
onfocusin={(e) => { onfocusin={(e) => {
changeIdx(idx); changeIdx(idx);
e.target.scrollIntoView({ block: 'center' }); e.target.scrollIntoView({ block: 'center' });
}} }}
> >
<td class="p-0.5 border">{item.b_id}</td> <td class="p-0.5 border">{item.b_id}</td>
<td class="p-0.5 border" <td class="p-0.5 border">{item.description}</td>
>{item.description}</td
>
<td class="p-0.5 border" <td class="p-0.5 border"
><input ><input
type="number" type="number"
@@ -201,18 +199,23 @@
const res = await fetch(`/api/tickets/${prefix.prefix}/${item.winning_ticket}`); const res = await fetch(`/api/tickets/${prefix.prefix}/${item.winning_ticket}`);
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
[item.last_name, item.first_name, item.phone_number] = [(data.last_name || ""), (data.first_name || ""), (data.phone_number || "")]; [item.last_name, item.first_name, item.phone_number] = [
data.last_name || '',
data.first_name || '',
data.phone_number || ''
];
} }
}} }}
bind:value={item.winning_ticket} bind:value={item.winning_ticket}
/></td /></td
> >
<td class="p-0.5 border"> <td class="p-0.5 border">
{item.last_name || ""}, {item.first_name || ""}: {item.phone_number || ""} {item.last_name || ''}, {item.first_name || ''}: {item.phone_number || ''}
</td> </td>
<td class="p-0.5 border" <td class="p-0.5 border"
><button ><button
class={bS[prefix.color]} class={bS[prefix.color]}
tabindex="-1"
onclick={() => { onclick={() => {
item.changed ? (item.changed = false) : (item.changed = true); item.changed ? (item.changed = false) : (item.changed = true);
}}>{item.changed ? 'Yes' : 'No'}</button }}>{item.changed ? 'Yes' : 'No'}</button
@@ -0,0 +1,11 @@
import { getSettings } from '$lib/server/settings/index.js';
export const load = async ({ params, fetch }) => {
const prefixRes = await fetch('/api/prefixes');
const prefixes = await prefixRes.json();
const prefix = Array.from(prefixes).find((p) => p.prefix == params.prefix);
const reportRes = await fetch(`/api/reports/bybasket/${params.prefix}`);
const reportLines = await reportRes.json();
const { venue_name } = getSettings();
return { prefixes, prefix, reportLines, venueName: venue_name };
};
@@ -0,0 +1,104 @@
<script>
import { resolve } from '$app/paths';
import HeaderBar from '$lib/client/components/HeaderBar.svelte';
import { bAS, bS } from '$lib/client/styles';
let currentFilter = $state('');
let { data } = $props();
let prefix = $derived(data.prefix);
let pageTitle = $derived(`${prefix.prefix} Winners by Name | TAM`);
let filterTitle = $state('All Winners');
const headers = ['Basket ID', 'Description', 'Winning Ticket', 'Winner Name', 'Phone Number'];
let reportLines = $derived.by(() => {
if (currentFilter == 'CALL') {
return data.reportLines.filter((l) => l.pref == 'CALL');
} else if (currentFilter == 'TEXT') {
return data.reportLines.filter((l) => l.pref == 'TEXT');
} else {
return data.reportLines;
}
});
</script>
<svelte:head>
<title>{pageTitle}</title>
</svelte:head>
<table class="w-full box-border border-separate p-1">
<thead>
<tr class="print:hidden">
<td colspan="50">
<HeaderBar>
<div>By Name Reports:</div>
{#each data.prefixes as p (p.prefix)}
<a
href={resolve('/reports/byname/[prefix]', { prefix: p.prefix })}
class={p.prefix == prefix.prefix ? bAS[p.color] : bS[p.color]}>{p.prefix}</a
>
{/each}
</HeaderBar>
<div class="flex flex-row gap-1 py-1 justify-between">
<div class="flex flex-row gap-1">
<button
class={bS[prefix.color]}
onclick={() => {
currentFilter = '';
filterTitle = 'All Winners';
}}>All Preferences</button
>
<button
class={bS[prefix.color]}
onclick={() => {
currentFilter = 'CALL';
filterTitle = 'Winners Preferring a CALL';
}}>Call Preference</button
>
<button
class={bS[prefix.color]}
onclick={() => {
currentFilter = 'TEXT';
filterTitle = 'Winners Preferring a TEXT';
}}>Text Preference</button
>
</div>
<div class="flex flex-row gap-1">
<button class={bS[prefix.color]} onclick={() => window.print()}>Print</button>
</div>
</div>
</td>
</tr>
<tr>
<th colspan="50"><h1 class="text-lg text-left">{pageTitle}</h1></th>
</tr>
<tr>
<th colspan="50"><h2 class="italic text-left">{filterTitle}</h2></th>
</tr>
<tr class="text-sm">
{#each headers as header (header)}
<th class="text-left border p-0.5">{header}</th>
{/each}
</tr>
</thead>
<tbody class="text-sm">
{#each reportLines as line, idx (idx)}
<tr>
<td class="p-0.5 border">{line.b_id}</td>
<td class="p-0.5 border">{line.description || ''}</td>
<td class="p-0.5 border">{line.winning_ticket}</td>
<td class="p-0.5 border">{line.last_name || ''}, {line.first_name || ''}</td>
<td class="p-0.5 border">{line.phone_number || ''}</td>
</tr>
{/each}
</tbody>
<tfoot>
<tr>
<td colspan="50" class="text-center text-xs">{data.venueName}</td>
</tr>
<tr>
<td colspan="50" class="text-center text-xs">&copy; 2026 Ticket Auction Manager</td>
</tr>
</tfoot>
</table>
@@ -0,0 +1,11 @@
import { getSettings } from '$lib/server/settings/index.js';
export const load = async ({ params, fetch }) => {
const prefixRes = await fetch('/api/prefixes');
const prefixes = await prefixRes.json();
const prefix = Array.from(prefixes).find((p) => p.prefix == params.prefix);
const reportRes = await fetch(`/api/reports/byname/${params.prefix}`);
const reportLines = await reportRes.json();
const { venue_name } = getSettings();
return { prefixes, prefix, reportLines, venueName: venue_name };
};
@@ -0,0 +1,104 @@
<script>
import { resolve } from '$app/paths';
import HeaderBar from '$lib/client/components/HeaderBar.svelte';
import { bAS, bS } from '$lib/client/styles';
let currentFilter = $state('');
let { data } = $props();
let prefix = $derived(data.prefix);
let pageTitle = $derived(`${prefix.prefix} Winners by Name | TAM`);
let filterTitle = $state('All Winners');
const headers = ['Winner Name', 'Phone Number', 'Basket ID', 'Winning Ticket', 'Description'];
let reportLines = $derived.by(() => {
if (currentFilter == 'CALL') {
return data.reportLines.filter((l) => l.pref == 'CALL');
} else if (currentFilter == 'TEXT') {
return data.reportLines.filter((l) => l.pref == 'TEXT');
} else {
return data.reportLines;
}
});
</script>
<svelte:head>
<title>{pageTitle}</title>
</svelte:head>
<table class="w-full box-border border-separate p-1">
<thead>
<tr class="print:hidden">
<td colspan="50">
<HeaderBar>
<div>By Name Reports:</div>
{#each data.prefixes as p (p.prefix)}
<a
href={resolve('/reports/byname/[prefix]', { prefix: p.prefix })}
class={p.prefix == prefix.prefix ? bAS[p.color] : bS[p.color]}>{p.prefix}</a
>
{/each}
</HeaderBar>
<div class="flex flex-row gap-1 py-1 justify-between">
<div class="flex flex-row gap-1">
<button
class={bS[prefix.color]}
onclick={() => {
currentFilter = '';
filterTitle = 'All Winners';
}}>All Preferences</button
>
<button
class={bS[prefix.color]}
onclick={() => {
currentFilter = 'CALL';
filterTitle = 'Winners Preferring a CALL';
}}>Call Preference</button
>
<button
class={bS[prefix.color]}
onclick={() => {
currentFilter = 'TEXT';
filterTitle = 'Winners Preferring a TEXT';
}}>Text Preference</button
>
</div>
<div class="flex flex-row gap-1">
<button class={bS[prefix.color]} onclick={() => window.print()}>Print</button>
</div>
</div>
</td>
</tr>
<tr>
<th colspan="50"><h1 class="text-lg text-left">{pageTitle}</h1></th>
</tr>
<tr>
<th colspan="50"><h2 class="italic text-left">{filterTitle}</h2></th>
</tr>
<tr class="text-sm">
{#each headers as header (header)}
<th class="text-left border p-0.5">{header}</th>
{/each}
</tr>
</thead>
<tbody class="text-sm">
{#each reportLines as line, idx (idx)}
<tr>
<td class="p-0.5 border">{line.last_name || ''}, {line.first_name || ''}</td>
<td class="p-0.5 border">{line.phone_number || ''}</td>
<td class="p-0.5 border">{line.b_id}</td>
<td class="p-0.5 border">{line.winning_ticket}</td>
<td class="p-0.5 border">{line.description || ''}</td>
</tr>
{/each}
</tbody>
<tfoot>
<tr>
<td colspan="50" class="text-center text-xs">{data.venueName}</td>
</tr>
<tr>
<td colspan="50" class="text-center text-xs">&copy; 2026 Ticket Auction Manager</td>
</tr>
</tfoot>
</table>
@@ -13,6 +13,8 @@
onMount(() => { onMount(() => {
prefixes = [...data.prefixes]; prefixes = [...data.prefixes];
const form_prefix = document.getElementById('form_prefix');
if (form_prefix) form_prefix.select();
}); });
</script> </script>
@@ -20,31 +22,31 @@
<title>{pageTitle}</title> <title>{pageTitle}</title>
</svelte:head> </svelte:head>
<div id="app_container" class="p-1">
<HeaderBar> <HeaderBar>
<a href={resolve('/settings')} class={bS.gray}>Back to Settings</a> <a href={resolve('/settings')} class={bS.gray}>Back to Settings</a>
</HeaderBar> </HeaderBar>
<div id="app_container" class="p-1">
<h1 class="text-xl font-bold">{pageTitle}</h1> <h1 class="text-xl font-bold">{pageTitle}</h1>
<div class="flex flex-row gap-1 py-1 items-center"> <div class="flex flex-row gap-1 py-1 items-center">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div>Prefix</div> <div>Prefix</div>
<input type="text" class={iS.normal} bind:value={editPrefix.prefix} /> <input type="text" id="form_prefix" class={iS.normal} bind:value={editPrefix.prefix} />
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div>Color</div> <div>Color</div>
<select name="prefix_color" class={iS.normal} bind:value={editPrefix.color}> <select id="form_color" class={iS.normal} bind:value={editPrefix.color}>
<option value="white">White</option> <option value="white">White</option>
<option value="blue">Blue</option> <option value="blue">Blue</option>
<option value="yellow">Yellow</option> <option value="yellow">Yellow</option>
<option value="green">Green</option> <option value="green">Green</option>
<option value="orange">Orange</option> <option value="orange">Orange</option>
<option value="purple">Purple</option>
<option value="red">Red</option> <option value="red">Red</option>
</select> </select>
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div>Weight</div> <div>Weight</div>
<input type="number" class={iS.normal} bind:value={editPrefix.weight} /> <input type="number" id="form_weight" class={iS.normal} bind:value={editPrefix.weight} />
</div> </div>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div>Actions</div> <div>Actions</div>
@@ -59,6 +61,9 @@
}); });
if (req.ok) window.location.reload(); if (req.ok) window.location.reload();
} }
editPrefix.prefix = '';
const form_prefix = document.getElementById('form_prefix');
if (form_prefix) form_prefix.select();
}}>Add/Change</button }}>Add/Change</button
> >
</div> </div>
@@ -85,27 +90,33 @@
class={editPrefix.color == 'orange' ? bAS.orange : bS.orange} class={editPrefix.color == 'orange' ? bAS.orange : bS.orange}
onclick={() => (editPrefix.color = 'orange')}>Orange</button onclick={() => (editPrefix.color = 'orange')}>Orange</button
> >
<button
class={editPrefix.color == 'purple' ? bAS.purple : bS.purple}
onclick={() => (editPrefix.color = 'purple')}>Purple</button
>
<button <button
class={editPrefix.color == 'red' ? bAS.red : bS.red} class={editPrefix.color == 'red' ? bAS.red : bS.red}
onclick={() => (editPrefix.color = 'red')}>Red</button onclick={() => (editPrefix.color = 'red')}>Red</button
> >
</div> </div>
<table class="w-full"> <table class="w-full border-separate">
<thead class="text-left"> <thead class="text-left">
<tr> <tr>
<th>Prefix</th> <th class="border p-0.5">Prefix</th>
<th>Color</th> <th class="border p-0.5">Color</th>
<th>Weight</th> <th class="border p-0.5">Weight</th>
<th>Actions</th> <th class="border p-0.5">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each prefixes as prefix (prefix.prefix)} {#each prefixes as prefix (prefix.prefix)}
<tr> <tr>
<td>{prefix.prefix}</td> <td class="border p-0.5">{prefix.prefix}</td>
<td>{prefix.color.charAt(0).toUpperCase() + prefix.color.slice(1)}</td> <td class="border p-0.5"
<td>{prefix.weight}</td> >{prefix.color.charAt(0).toUpperCase() + prefix.color.slice(1)}</td
<td> >
<td class="border p-0.5">{prefix.weight}</td>
<td class="border p-0.5">
<div class="flex flex-row gap-1 items-center"> <div class="flex flex-row gap-1 items-center">
<button class={bS[prefix.color]} onclick={() => (editPrefix = { ...prefix })} <button class={bS[prefix.color]} onclick={() => (editPrefix = { ...prefix })}
>Edit</button >Edit</button
@@ -126,11 +137,3 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<style>
table td,
table th {
border: solid black 1px;
padding: 0.25rem;
}
</style>
@@ -2,7 +2,7 @@
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { afterNavigate, beforeNavigate } from '$app/navigation'; import { afterNavigate, beforeNavigate } from '$app/navigation';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { bS, bAS, iS } from '$lib/client/styles'; import { bS, bAS, iS, rBS } from '$lib/client/styles';
import HeaderBar from '$lib/client/components/HeaderBar.svelte'; import HeaderBar from '$lib/client/components/HeaderBar.svelte';
import PagerBar from '$lib/client/components/PagerBar.svelte'; import PagerBar from '$lib/client/components/PagerBar.svelte';
import CommandBar from '$lib/client/components/CommandBar.svelte'; import CommandBar from '$lib/client/components/CommandBar.svelte';
@@ -28,7 +28,7 @@
let pager = $state({ idFrom: 0, idTo: 0 }); let pager = $state({ idFrom: 0, idTo: 0 });
let items = $state([]); let items = $state([]);
let itemsLength = $derived(items.length); let itemsLength = $derived.by(() => (items.length ? items.length : 0));
let itemsBuffer = $derived(items.filter((i) => i.changed)); let itemsBuffer = $derived(items.filter((i) => i.changed));
const functions = { const functions = {
getPage: async () => { getPage: async () => {
@@ -181,7 +181,7 @@
<tbody> <tbody>
{#each items as item, idx (item.t_id)} {#each items as item, idx (item.t_id)}
<tr <tr
class="focus-within:font-bold" class="focus-within:font-bold {rBS[prefix.color]}"
onfocusin={(e) => { onfocusin={(e) => {
changeIdx(idx); changeIdx(idx);
e.target.scrollIntoView({ block: 'center' }); e.target.scrollIntoView({ block: 'center' });