(add): Baskets function
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,65 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from db import RepoTemplate
|
||||||
|
from system.auth import AuthRepo
|
||||||
|
from fastapi import APIRouter, Header
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Basket:
|
||||||
|
prefix: str
|
||||||
|
b_id: int
|
||||||
|
description: str = ""
|
||||||
|
donors: str = ""
|
||||||
|
winning_ticket: int = 0
|
||||||
|
|
||||||
|
class BasketRepo(RepoTemplate):
|
||||||
|
def get_all_baskets(self):
|
||||||
|
self.cur.execute("SELECT * FROM baskets ORDER BY prefix, b_id")
|
||||||
|
results = self.cur.fetchall()
|
||||||
|
return [Basket(*r) for r in results]
|
||||||
|
def get_prefix_baskets(self, prefix: str):
|
||||||
|
self.cur.execute("SELECT * FROM baskets WHERE prefix = ? ORDER BY prefix, b_id", (prefix,))
|
||||||
|
results = self.cur.fetchall()
|
||||||
|
return [Basket(*r) for r in results]
|
||||||
|
def get_single_basket(self, prefix: str, b_id: int):
|
||||||
|
self.cur.execute("SELECT * FROM baskets WHERE prefix = ? AND b_id = ? ORDER BY prefix, b_id", (prefix, b_id))
|
||||||
|
results = self.cur.fetchall()
|
||||||
|
return [Basket(*r) for r in results]
|
||||||
|
def get_range_baskets(self, prefix: str, id_from: int, id_to: int):
|
||||||
|
self.cur.execute("""SELECT * FROM baskets WHERE prefix = ? AND b_id BETWEEN ? AND ?
|
||||||
|
ORDER BY prefix, b_id""", (prefix, id_from, id_to))
|
||||||
|
results = self.cur.fetchall()
|
||||||
|
return [Basket(*r) for r in results]
|
||||||
|
def post_baskets(self, bs: list[Basket]):
|
||||||
|
for b in bs:
|
||||||
|
self.cur.execute("""INSERT INTO baskets VALUES (?, ?, ?, ?, ?) ON CONFLICT (prefix, b_id) DO UPDATE SET
|
||||||
|
description = EXCLUDED.description, donors = EXCLUDED.donors""",
|
||||||
|
(b.prefix, b.b_id, b.description, b.donors, b.winning_ticket))
|
||||||
|
self.conn.commit()
|
||||||
|
return bs
|
||||||
|
|
||||||
|
basket_router = APIRouter(prefix="/api/baskets")
|
||||||
|
|
||||||
|
@basket_router.get("")
|
||||||
|
def get_all_baskets(tam_key: str = Header("")):
|
||||||
|
AuthRepo().verify_key(tam_key)
|
||||||
|
return BasketRepo().get_all_baskets()
|
||||||
|
|
||||||
|
@basket_router.get("/{prefix}")
|
||||||
|
def get_prefix_baskets(prefix: str, tam_key: str = Header("")):
|
||||||
|
AuthRepo().verify_key(tam_key)
|
||||||
|
return BasketRepo().get_prefix_baskets(prefix)
|
||||||
|
|
||||||
|
@basket_router.get("/{prefix}/{b_id}")
|
||||||
|
def get_single_basket(prefix: str, b_id: int, tam_key: str = Header("")):
|
||||||
|
AuthRepo().verify_key(tam_key)
|
||||||
|
return BasketRepo().get_single_basket(prefix, b_id)
|
||||||
|
|
||||||
|
@basket_router.get("/{prefix}/{id_from}/{id_to}")
|
||||||
|
def get_range_baskets(prefix: str, id_from: int, id_to: int, tam_key: str = Header("")):
|
||||||
|
AuthRepo().verify_key(tam_key)
|
||||||
|
return BasketRepo().get_range_baskets(prefix, id_from, id_to)
|
||||||
|
|
||||||
|
@basket_router.post("")
|
||||||
|
def post_baskets(bs: list[Basket], tam_key: str = Header("")):
|
||||||
|
AuthRepo().verify_key(tam_key)
|
||||||
|
return BasketRepo().post_baskets(bs)
|
||||||
@@ -19,6 +19,8 @@ def init_db():
|
|||||||
cur.execute("CREATE TABLE IF NOT EXISTS prefixes (prefix TEXT PRIMARY KEY, color TEXT, weight INTEGER)")
|
cur.execute("CREATE TABLE IF NOT EXISTS prefixes (prefix TEXT PRIMARY KEY, color TEXT, weight INTEGER)")
|
||||||
cur.execute("""CREATE TABLE IF NOT EXISTS tickets (prefix TEXT, t_id INTEGER, first_name TEXT, last_name TEXT,
|
cur.execute("""CREATE TABLE IF NOT EXISTS tickets (prefix TEXT, t_id INTEGER, first_name TEXT, last_name TEXT,
|
||||||
phone_number TEXT, pref TEXT, PRIMARY KEY (prefix, t_id))""")
|
phone_number TEXT, pref TEXT, PRIMARY KEY (prefix, t_id))""")
|
||||||
|
cur.execute("""CREATE TABLE IF NOT EXISTS baskets (prefix TEXT, b_id INTEGER, description TEXT, donors TEXT,
|
||||||
|
winning_ticket INTEGER, PRIMARY KEY (prefix, b_id))""")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ from fastapi import FastAPI
|
|||||||
from system.auth import auth_router
|
from system.auth import auth_router
|
||||||
from system.prefixes import prefix_router
|
from system.prefixes import prefix_router
|
||||||
from data.tickets import tickets_router
|
from data.tickets import tickets_router
|
||||||
|
from data.baskets import basket_router
|
||||||
|
|
||||||
def imp_routers(app: FastAPI):
|
def imp_routers(app: FastAPI):
|
||||||
app.include_router(auth_router)
|
app.include_router(auth_router)
|
||||||
app.include_router(prefix_router)
|
app.include_router(prefix_router)
|
||||||
app.include_router(tickets_router)
|
app.include_router(tickets_router)
|
||||||
|
app.include_router(basket_router)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineConfig } from 'drizzle-kit';
|
import { defineConfig } from 'drizzle-kit';
|
||||||
|
|
||||||
const dbURL = (process.env.TAM_DATA_DIR || "./data") + "/tam-local.db"
|
const dbURL = (process.env.TAM_DATA_DIR || './data') + '/tam-local.db';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
schema: './src/lib/server/db/schema.js',
|
schema: './src/lib/server/db/schema.js',
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
<meta name="text-scale" content="scale" />
|
<meta name="text-scale" content="scale" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="tap">
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import { bS } from "../styles";
|
import { bS } from '../styles';
|
||||||
import { browser } from "$app/environment";
|
import { browser } from '$app/environment';
|
||||||
import hotkeys from "hotkeys-js";
|
import hotkeys from 'hotkeys-js';
|
||||||
|
|
||||||
let { prefix, functions } = $props();
|
let { prefix, functions } = $props();
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
hotkeys.filter = () => {return true};
|
hotkeys.filter = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
hotkeys('alt+l', (e) => {
|
hotkeys('alt+l', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (functions.nextLine) functions.nextLine();
|
if (functions.nextLine) functions.nextLine();
|
||||||
@@ -41,27 +43,40 @@
|
|||||||
<div class="flex flex-row gap-1 py-1 justify-between">
|
<div class="flex flex-row gap-1 py-1 justify-between">
|
||||||
<div class="flex flex-row gap-1">
|
<div class="flex flex-row gap-1">
|
||||||
{#if functions.dupDown}
|
{#if functions.dupDown}
|
||||||
<button class={bS[prefix.color]} title="Alt + J" onclick={() => functions.dupDown()}>Duplicate Down</button>
|
<button class={bS[prefix.color]} title="Alt + J" onclick={() => functions.dupDown()}
|
||||||
|
>Duplicate Down</button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if functions.dupUp}
|
{#if functions.dupUp}
|
||||||
<button class={bS[prefix.color]} title="Alt + U" onclick={() => functions.dupUp()}>Duplicate Up</button>
|
<button class={bS[prefix.color]} title="Alt + U" onclick={() => functions.dupUp()}
|
||||||
|
>Duplicate Up</button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if functions.nextLine}
|
{#if functions.nextLine}
|
||||||
<button class={bS[prefix.color]} title="Alt + L" onclick={() => functions.nextLine()}>Next Line</button>
|
<button class={bS[prefix.color]} title="Alt + L" onclick={() => functions.nextLine()}
|
||||||
|
>Next Line</button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if functions.prevLine}
|
{#if functions.prevLine}
|
||||||
<button class={bS[prefix.color]} title="Alt + O" onclick={() => functions.prevLine()}>Previous Line</button>
|
<button class={bS[prefix.color]} title="Alt + O" onclick={() => functions.prevLine()}
|
||||||
|
>Previous Line</button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if functions.copy}
|
{#if functions.copy}
|
||||||
<button class={bS[prefix.color]} title="Alt + C" onclick={() => functions.copy()}>Copy</button>
|
<button class={bS[prefix.color]} title="Alt + C" onclick={() => functions.copy()}>Copy</button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if functions.paste}
|
{#if functions.paste}
|
||||||
<button class={bS[prefix.color]} title="Alt + V" onclick={() => functions.paste()}>Paste</button>
|
<button class={bS[prefix.color]} title="Alt + V" onclick={() => functions.paste()}
|
||||||
|
>Paste</button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-1">
|
<div class="flex flex-row gap-1">
|
||||||
{#if functions.save}
|
{#if functions.save}
|
||||||
<button class={bS[prefix.color]} title="Alt + S" onclick={() => functions.save()}>Save Marked</button>
|
<button class={bS[prefix.color]} title="Alt + S" onclick={() => functions.save()}
|
||||||
|
>Save Marked</button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="header_bar" class="p-1 flex flex-row gap-1 items-center">
|
<div id="header_bar" class="py-1 flex flex-row gap-1 items-center">
|
||||||
<a href={resolve('/')} class="{bS.gray} font-bold">Main Menu</a>
|
<a href={resolve('/')} class="{bS.gray} font-bold">Main Menu</a>
|
||||||
{#if children}
|
{#if children}
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -2,28 +2,33 @@ export const tS = {
|
|||||||
green: 'text-green-800 font-bold',
|
green: 'text-green-800 font-bold',
|
||||||
yellow: 'text-yellow-800 font-bold',
|
yellow: 'text-yellow-800 font-bold',
|
||||||
red: 'text-red-800 font-bold'
|
red: 'text-red-800 font-bold'
|
||||||
}
|
};
|
||||||
|
|
||||||
export const bS = {
|
export const bS = {
|
||||||
gray: 'px-2 py-1 bg-gray-300 border border-gray-800 rounded hover:bg-gray-400 cursor-pointer',
|
gray: 'px-2 py-1 bg-gray-300 border border-gray-800 rounded hover:bg-gray-400 cursor-pointer',
|
||||||
white: 'px-2 py-1 bg-white border border-gray-700 rounded hover:bg-gray-200 cursor-pointer',
|
white: 'px-2 py-1 bg-white border border-gray-700 rounded hover:bg-gray-200 cursor-pointer',
|
||||||
blue: 'px-2 py-1 bg-blue-300 border border-gray-700 rounded hover:bg-blue-400 cursor-pointer',
|
blue: 'px-2 py-1 bg-blue-300 border border-gray-700 rounded hover:bg-blue-400 cursor-pointer',
|
||||||
yellow: 'px-2 py-1 bg-yellow-300 border border-gray-700 rounded hover:bg-yellow-400 cursor-pointer',
|
yellow:
|
||||||
|
'px-2 py-1 bg-yellow-300 border border-gray-700 rounded hover:bg-yellow-400 cursor-pointer',
|
||||||
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: 'px-2 py-1 bg-orange-300 border border-gray-700 rounded hover:bg-orange-400 cursor-pointer',
|
orange:
|
||||||
|
'px-2 py-1 bg-orange-300 border border-gray-700 rounded hover:bg-orange-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'
|
||||||
}
|
};
|
||||||
|
|
||||||
export const bAS = {
|
export const bAS = {
|
||||||
gray: 'px-2 py-1 bg-gray-300 border-4 border-gray-800 rounded hover:bg-gray-400 cursor-pointer',
|
gray: 'px-2 py-1 bg-gray-300 border-4 border-gray-800 rounded hover:bg-gray-400 cursor-pointer',
|
||||||
white: 'px-2 py-1 bg-white border-4 border-gray-700 rounded hover:bg-gray-200 cursor-pointer',
|
white: 'px-2 py-1 bg-white border-4 border-gray-700 rounded hover:bg-gray-200 cursor-pointer',
|
||||||
blue: 'px-2 py-1 bg-blue-300 border-4 border-gray-700 rounded hover:bg-blue-400 cursor-pointer',
|
blue: 'px-2 py-1 bg-blue-300 border-4 border-gray-700 rounded hover:bg-blue-400 cursor-pointer',
|
||||||
yellow: 'px-2 py-1 bg-yellow-300 border-4 border-gray-700 rounded hover:bg-yellow-400 cursor-pointer',
|
yellow:
|
||||||
green: 'px-2 py-1 bg-green-300 border-4 border-gray-700 rounded hover:bg-green-400 cursor-pointer',
|
'px-2 py-1 bg-yellow-300 border-4 border-gray-700 rounded hover:bg-yellow-400 cursor-pointer',
|
||||||
orange: 'px-2 py-1 bg-orange-300 border-4 border-gray-700 rounded hover:bg-orange-400 cursor-pointer',
|
green:
|
||||||
|
'px-2 py-1 bg-green-300 border-4 border-gray-700 rounded hover:bg-green-400 cursor-pointer',
|
||||||
|
orange:
|
||||||
|
'px-2 py-1 bg-orange-300 border-4 border-gray-700 rounded hover:bg-orange-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'
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Database from 'better-sqlite3';
|
|||||||
import * as schema from './schema';
|
import * as schema from './schema';
|
||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
|
|
||||||
const dbURL = (env.TAM_DATA_DIR || "./data") + "/tam-local.db";
|
const dbURL = (env.TAM_DATA_DIR || './data') + '/tam-local.db';
|
||||||
|
|
||||||
const client = new Database(dbURL);
|
const client = new Database(dbURL);
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,27 @@ export const prefixes = sqliteTable('prefixes', {
|
|||||||
weight: integer('weight')
|
weight: integer('weight')
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tickets = sqliteTable('tickets', {
|
export const tickets = sqliteTable(
|
||||||
|
'tickets',
|
||||||
|
{
|
||||||
prefix: text('prefix'),
|
prefix: text('prefix'),
|
||||||
t_id: integer('t_id'),
|
t_id: integer('t_id'),
|
||||||
first_name: text('first_name'),
|
first_name: text('first_name'),
|
||||||
last_name: text('last_name'),
|
last_name: text('last_name'),
|
||||||
phone_number: text('phone_number'),
|
phone_number: text('phone_number'),
|
||||||
pref: text('pref')
|
pref: text('pref')
|
||||||
}, (t) => [primaryKey({columns: [t.prefix, t.t_id]})])
|
},
|
||||||
|
(t) => [primaryKey({ columns: [t.prefix, t.t_id] })]
|
||||||
|
);
|
||||||
|
|
||||||
|
export const baskets = sqliteTable(
|
||||||
|
'baskets',
|
||||||
|
{
|
||||||
|
prefix: text('prefix'),
|
||||||
|
b_id: integer('b_id'),
|
||||||
|
description: text('description'),
|
||||||
|
donors: text('donors'),
|
||||||
|
winning_ticket: integer('winning_ticket')
|
||||||
|
},
|
||||||
|
(b) => [primaryKey({ columns: [b.prefix, b.b_id] })]
|
||||||
|
);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const getSettings = () => {
|
|||||||
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||||
return settings;
|
return settings;
|
||||||
} catch {
|
} catch {
|
||||||
mkdirSync(env.TAM_DATA_DIR || './data', {recursive: true})
|
mkdirSync(env.TAM_DATA_DIR || './data', { recursive: true });
|
||||||
writeFileSync(settingsPath, JSON.stringify(defaultSettings, null, 2), 'utf-8');
|
writeFileSync(settingsPath, JSON.stringify(defaultSettings, null, 2), 'utf-8');
|
||||||
return defaultSettings;
|
return defaultSettings;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,6 @@
|
|||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<link rel="icon" href={favicon} />
|
<link rel="icon" href={favicon} />
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json" />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import hotkeys from 'hotkeys-js';
|
import hotkeys from 'hotkeys-js';
|
||||||
|
|
||||||
const pageTitle = 'Main Menu | TAM'
|
const pageTitle = 'Main Menu | TAM';
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
let adminMode = $state(false);
|
let adminMode = $state(false);
|
||||||
let prefixes = $derived(data.prefixes);
|
let prefixes = $derived(data.prefixes);
|
||||||
let curPrefix = $state("");
|
let curPrefix = $state('');
|
||||||
let pColor = $derived.by(() => {
|
let pColor = $derived.by(() => {
|
||||||
if (curPrefix) return prefixes.find(p => curPrefix == p.prefix).color
|
if (curPrefix) return prefixes.find((p) => curPrefix == p.prefix).color;
|
||||||
else return "gray";
|
else return 'gray';
|
||||||
})
|
});
|
||||||
|
|
||||||
const status = $derived.by(() => {
|
const status = $derived.by(() => {
|
||||||
if (data.whoami === 'TAM Server') {
|
if (data.whoami === 'TAM Server') {
|
||||||
@@ -28,16 +28,18 @@
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
mode: 'Unknown'
|
mode: 'Unknown'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
})
|
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
hotkeys.filter = () => {return true};
|
hotkeys.filter = () => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
hotkeys('alt+a', (event) => {
|
hotkeys('alt+a', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
adminMode = !adminMode;
|
adminMode = !adminMode;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -46,21 +48,29 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="p-1" id="app_container">
|
<div class="p-1" id="app_container">
|
||||||
<h1 class="text-xl font-bold">{pageTitle}</h1>
|
<h1 class="text-xl font-bold">{pageTitle}</h1>
|
||||||
<p class="text-lg italic">{data.venueName}</p>
|
<p class="text-lg italic">{data.venueName}</p>
|
||||||
|
|
||||||
<div class="flex flex-col md:flex-row md:flex-wrap gap-1 py-1">
|
<div class="flex flex-col md:flex-row md:flex-wrap gap-1 py-1">
|
||||||
<div id="prefixes" class="flex flex-col gap-1 p-2 border border-black rounded">
|
<div id="prefixes" class="flex flex-col gap-1 p-2 border border-black rounded">
|
||||||
<h2 class="text-lg font-bold">Prefix Selection:</h2>
|
<h2 class="text-lg font-bold">Prefix Selection:</h2>
|
||||||
{#each prefixes as prefix (prefix.prefix)}
|
{#each prefixes as prefix (prefix.prefix)}
|
||||||
<button class={curPrefix == prefix.prefix ? bAS[prefix.color] : bS[prefix.color]} onclick={() => curPrefix = prefix.prefix}>{prefix.prefix}</button>
|
<button
|
||||||
|
class={curPrefix == prefix.prefix ? bAS[prefix.color] : bS[prefix.color]}
|
||||||
|
onclick={() => (curPrefix = prefix.prefix)}>{prefix.prefix}</button
|
||||||
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{#if curPrefix}
|
{#if curPrefix}
|
||||||
<div class="flex flex-col gap-1 items-center border border-black rounded">
|
<div class="flex flex-col gap-1 items-center border border-black rounded">
|
||||||
<h2 class="text-lg font-bold">Forms:</h2>
|
<h2 class="text-lg font-bold">Forms:</h2>
|
||||||
<div class="grid grid-cols-2 gap-1 p-1 min-w-2xs">
|
<div class="grid grid-cols-2 gap-1 p-1 min-w-2xs">
|
||||||
<a href={resolve('/tickets/[prefix]', {prefix: curPrefix})} class="{bS[pColor]}">Tickets</a>
|
<a href={resolve('/tickets/[prefix]', { prefix: curPrefix })} class={bS[pColor]}
|
||||||
|
>Tickets</a
|
||||||
|
>
|
||||||
|
<a href={resolve('/baskets/[prefix]', { prefix: curPrefix })} class={bS[pColor]}
|
||||||
|
>Baskets</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -68,27 +78,31 @@
|
|||||||
<h2 class="text-lg font-bold">Please select a prefix to continue.</h2>
|
<h2 class="text-lg font-bold">Please select a prefix to continue.</h2>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if adminMode}
|
{#if adminMode}
|
||||||
<div id="admin_mode" class="py-1">
|
<div id="admin_mode" class="py-1">
|
||||||
<h2 class="text-lg font-bold">Admin Mode:</h2>
|
<h2 class="text-lg font-bold">Admin Mode:</h2>
|
||||||
<div class="flex flex-row gap-1">
|
<div class="flex flex-row gap-1">
|
||||||
<a href={resolve('/settings')} class={bS.gray}>Settings</a>
|
<a href={resolve('/settings')} class={bS.gray}>Settings</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div>Mode: {status.mode}</div>
|
<div>Mode: {status.mode}</div>
|
||||||
{#if data.authenticated !== undefined}
|
{#if data.authenticated !== undefined}
|
||||||
<div>Authenticated: <span class={tS[status.auth]}>{data.authenticated ? 'Yes' : 'No'}</span></div>
|
<div>
|
||||||
|
Authenticated: <span class={tS[status.auth]}>{data.authenticated ? 'Yes' : 'No'}</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if data.healthy !== undefined}
|
{#if data.healthy !== undefined}
|
||||||
<div>Server Healthy: <span class={tS[status.healthy]}>{data.healthy ? 'Yes' : 'No'}</span></div>
|
<div>
|
||||||
|
Server Healthy: <span class={tS[status.healthy]}>{data.healthy ? 'Yes' : 'No'}</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="text-center text-xs">
|
<div class="text-center text-xs">
|
||||||
<p>© 2026 Ticket Auction Manager</p>
|
<p>© 2026 Ticket Auction Manager</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const GET = async () => {
|
|||||||
headers: { 'TAM-KEY': s.remote_key }
|
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);
|
||||||
} catch {
|
} catch {
|
||||||
const data = { whoami: 'TAM Server', authenticated: false, healthy: false };
|
const data = { whoami: 'TAM Server', authenticated: false, healthy: false };
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getSettings, getPath } from "$lib/server/settings";
|
import { getSettings, getPath } from '$lib/server/settings';
|
||||||
import { json, error } from "@sveltejs/kit";
|
import { json, error } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const GET = async ({ request }) => {
|
export const GET = async ({ request }) => {
|
||||||
const tamPwd = request.headers.get('TAM-PWD');
|
const tamPwd = request.headers.get('TAM-PWD');
|
||||||
@@ -14,12 +14,12 @@ export const GET = async ({ request }) => {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
return json(data);
|
return json(data);
|
||||||
} catch {
|
} catch {
|
||||||
throw error(502)
|
throw error(502);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw error(500, 'Not configured.')
|
throw error(500, 'Not configured.');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const POST = async ({ request }) => {
|
export const POST = async ({ request }) => {
|
||||||
const tamPwd = request.headers.get('TAM-PWD');
|
const tamPwd = request.headers.get('TAM-PWD');
|
||||||
@@ -40,9 +40,9 @@ export const POST = async ({ request }) => {
|
|||||||
throw error(502);
|
throw error(502);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw error(500, 'Not configured.')
|
throw error(500, 'Not configured.');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const DELETE = async ({ request, url }) => {
|
export const DELETE = async ({ request, url }) => {
|
||||||
const tamPwd = request.headers.get('TAM-PWD');
|
const tamPwd = request.headers.get('TAM-PWD');
|
||||||
@@ -54,14 +54,14 @@ export const DELETE = async ({ request, url }) => {
|
|||||||
const res = await fetch(`${connStr}/api/auth?key_to_del=${keyToDel}`, {
|
const res = await fetch(`${connStr}/api/auth?key_to_del=${keyToDel}`, {
|
||||||
headers: { 'TAM-PW': tamPwd },
|
headers: { 'TAM-PW': tamPwd },
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
});
|
||||||
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);
|
||||||
} catch {
|
} catch {
|
||||||
throw error(502);
|
throw error(502);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw error(500, 'Not configured.')
|
throw error(500, 'Not configured.');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { db } from '$lib/server/db';
|
||||||
|
import { baskets } from '$lib/server/db/schema';
|
||||||
|
import { getSettings, getPath } from '$lib/server/settings';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import { sql } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const GET = async () => {
|
||||||
|
const s = getSettings();
|
||||||
|
if (s.remote_server) {
|
||||||
|
const connStr = getPath(s);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${connStr}/api/baskets`, { headers: { 'TAM-KEY': s.remote_key } });
|
||||||
|
if (!res.ok) throw error(res.status);
|
||||||
|
const data = await res.json();
|
||||||
|
return json(data);
|
||||||
|
} catch {
|
||||||
|
return json([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const data = await db.select().from(baskets).orderBy(baskets.prefix, baskets.b_id);
|
||||||
|
return json(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST = async ({ request }) => {
|
||||||
|
const reqData = await request.json();
|
||||||
|
const s = getSettings();
|
||||||
|
if (s.remote_server) {
|
||||||
|
const connStr = getPath(s);
|
||||||
|
const res = await fetch(`${connStr}/api/baskets`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'TAM-KEY': s.remote_key, 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(reqData)
|
||||||
|
});
|
||||||
|
if (!res.ok) throw error(res.status);
|
||||||
|
}
|
||||||
|
await db
|
||||||
|
.insert(baskets)
|
||||||
|
.values(reqData)
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: [baskets.prefix, baskets.b_id],
|
||||||
|
set: { description: sql`EXCLUDED.description`, donors: sql`EXCLUDED.donors` }
|
||||||
|
});
|
||||||
|
return json(reqData);
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { db } from '$lib/server/db/index.js';
|
||||||
|
import { baskets } from '$lib/server/db/schema.js';
|
||||||
|
import { getPath, getSettings } from '$lib/server/settings';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const GET = async ({ params }) => {
|
||||||
|
const { prefix } = params;
|
||||||
|
const s = getSettings();
|
||||||
|
if (s.remote_server) {
|
||||||
|
const connStr = getPath(s);
|
||||||
|
try {
|
||||||
|
const req = await fetch(`${connStr}/api/baskets/${prefix}`, {
|
||||||
|
headers: { 'TAM-KEY': s.remote_key }
|
||||||
|
});
|
||||||
|
if (!req.ok) throw error(req.status);
|
||||||
|
const data = await req.json();
|
||||||
|
return json(data);
|
||||||
|
} catch {
|
||||||
|
return json([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const data = await db
|
||||||
|
.select()
|
||||||
|
.from(baskets)
|
||||||
|
.where(eq(baskets.prefix, prefix))
|
||||||
|
.orderBy(baskets.prefix, baskets.b_id);
|
||||||
|
return json(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { db } from '$lib/server/db/index.js';
|
||||||
|
import { baskets } from '$lib/server/db/schema.js';
|
||||||
|
import { getPath, getSettings } from '$lib/server/settings';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import { and, eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const GET = async ({ params }) => {
|
||||||
|
const [prefix, b_id] = [params.prefix, parseInt(params.b_id)];
|
||||||
|
const s = getSettings();
|
||||||
|
if (s.remote_server) {
|
||||||
|
const connStr = getPath(s);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${connStr}/api/tickets/${prefix}/${b_id}`, {
|
||||||
|
headers: { 'TAM-KEY': s.remote_key }
|
||||||
|
});
|
||||||
|
if (!res.ok) throw error(res.status);
|
||||||
|
const [data] = await res.json();
|
||||||
|
if (data) {
|
||||||
|
return json(data);
|
||||||
|
} else {
|
||||||
|
return json({ prefix, b_id, description: '', donors: '', winning_ticket: 0 });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return json({ prefix, b_id, description: '', donors: '', winning_ticket: 0 });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const [data] = await db
|
||||||
|
.select()
|
||||||
|
.from(baskets)
|
||||||
|
.where(and(eq(baskets.prefix, prefix), eq(baskets.b_id, b_id)))
|
||||||
|
.orderBy(baskets.prefix, baskets.b_id);
|
||||||
|
if (data) {
|
||||||
|
return json(data);
|
||||||
|
} else {
|
||||||
|
return json({ prefix, b_id, descrption: '', donors: '', winning_ticket: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { db } from '$lib/server/db/index.js';
|
||||||
|
import { baskets } from '$lib/server/db/schema.js';
|
||||||
|
import { getPath, getSettings } from '$lib/server/settings';
|
||||||
|
import { error, json } from '@sveltejs/kit';
|
||||||
|
import { and, between, eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const GET = async ({ params }) => {
|
||||||
|
const s = getSettings();
|
||||||
|
const [prefix, id_from, id_to] = [
|
||||||
|
params.prefix,
|
||||||
|
parseInt(params.id_from),
|
||||||
|
parseInt(params.id_to)
|
||||||
|
];
|
||||||
|
const rtnData = {};
|
||||||
|
for (let i = id_from; i <= id_to; i++) {
|
||||||
|
rtnData[i] = { prefix, b_id: i, description: '', donors: '', winning_ticket: 0 };
|
||||||
|
}
|
||||||
|
if (s.remote_server) {
|
||||||
|
const connStr = getPath(s);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${connStr}/api/baskets/${prefix}/${id_from}/${id_to}`, {
|
||||||
|
headers: { 'TAM-KEY': s.remote_key }
|
||||||
|
});
|
||||||
|
if (!res.ok) throw error(res.status);
|
||||||
|
const data = Array.from(await res.json());
|
||||||
|
data.forEach((b) => (rtnData[b.b_id] = b));
|
||||||
|
} catch {
|
||||||
|
return json([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const data = await db
|
||||||
|
.select()
|
||||||
|
.from(baskets)
|
||||||
|
.where(and(eq(baskets.prefix, prefix), between(baskets.b_id, id_from, id_to)))
|
||||||
|
.orderBy(baskets.prefix, baskets.b_id);
|
||||||
|
data.forEach((b) => (rtnData[b.b_id] = b));
|
||||||
|
}
|
||||||
|
return json(Object.values(rtnData));
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { getSettings, getPath } from "$lib/server/settings"
|
import { getSettings, getPath } from '$lib/server/settings';
|
||||||
import { json, error } from "@sveltejs/kit";
|
import { json, error } from '@sveltejs/kit';
|
||||||
import { db } from "$lib/server/db";
|
import { db } from '$lib/server/db';
|
||||||
import { prefixes } from "$lib/server/db/schema";
|
import { prefixes } from '$lib/server/db/schema';
|
||||||
import { sql, eq } from "drizzle-orm";
|
import { sql, eq } from 'drizzle-orm';
|
||||||
|
|
||||||
export const GET = async () => {
|
export const GET = async () => {
|
||||||
const s = getSettings();
|
const s = getSettings();
|
||||||
@@ -22,7 +22,7 @@ export const GET = async () => {
|
|||||||
const data = await db.select().from(prefixes).orderBy(prefixes.weight, prefixes.prefix);
|
const data = await db.select().from(prefixes).orderBy(prefixes.weight, prefixes.prefix);
|
||||||
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();
|
||||||
@@ -33,12 +33,18 @@ export const POST = async ({ request }) => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'TAM-KEY': s.remote_key, 'Content-Type': 'application/json' },
|
headers: { 'TAM-KEY': s.remote_key, 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(reqData)
|
body: JSON.stringify(reqData)
|
||||||
})
|
});
|
||||||
if (!res.ok) throw error(res.status);
|
if (!res.ok) throw error(res.status);
|
||||||
}
|
}
|
||||||
await db.insert(prefixes).values(reqData).onConflictDoUpdate({ target: prefixes.prefix, set: { color: sql`EXCLUDED.color`, weight: sql`EXCLUDED.weight` } });
|
await db
|
||||||
|
.insert(prefixes)
|
||||||
|
.values(reqData)
|
||||||
|
.onConflictDoUpdate({
|
||||||
|
target: prefixes.prefix,
|
||||||
|
set: { color: sql`EXCLUDED.color`, weight: sql`EXCLUDED.weight` }
|
||||||
|
});
|
||||||
return json(reqData);
|
return json(reqData);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const DELETE = async ({ url }) => {
|
export const DELETE = async ({ url }) => {
|
||||||
const p = url.searchParams.get('p');
|
const p = url.searchParams.get('p');
|
||||||
@@ -52,5 +58,5 @@ export const DELETE = async ({ url }) => {
|
|||||||
if (!res.ok) throw error(res.status);
|
if (!res.ok) throw error(res.status);
|
||||||
}
|
}
|
||||||
await db.delete(prefixes).where(eq(prefixes.prefix, p));
|
await db.delete(prefixes).where(eq(prefixes.prefix, p));
|
||||||
return json({ message: "Deleted successfully."});
|
return json({ message: 'Deleted successfully.' });
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { setSettings } from "$lib/server/settings";
|
import { setSettings } from '$lib/server/settings';
|
||||||
import { json } from "@sveltejs/kit";
|
import { json } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const POST = async ({ request }) => {
|
export const POST = async ({ request }) => {
|
||||||
const newSettings = await request.json();
|
const newSettings = await request.json();
|
||||||
setSettings(newSettings);
|
setSettings(newSettings);
|
||||||
return json(newSettings);
|
return json(newSettings);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -32,7 +32,11 @@ export const GET = async ({ params }) => {
|
|||||||
return json([]);
|
return json([]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const data = await db.select().from(tickets).where(and(eq(tickets.prefix, prefix), between(tickets.t_id, id_from, id_to))).orderBy(tickets.prefix, tickets.t_id);
|
const data = await db
|
||||||
|
.select()
|
||||||
|
.from(tickets)
|
||||||
|
.where(and(eq(tickets.prefix, prefix), between(tickets.t_id, id_from, id_to)))
|
||||||
|
.orderBy(tickets.prefix, tickets.t_id);
|
||||||
data.forEach((t) => (rtnData[t.t_id] = t));
|
data.forEach((t) => (rtnData[t.t_id] = t));
|
||||||
}
|
}
|
||||||
return json(Object.values(rtnData));
|
return json(Object.values(rtnData));
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export const load = async ({ params, fetch }) => {
|
||||||
|
const { prefix } = params;
|
||||||
|
const res = await fetch('/api/prefixes');
|
||||||
|
const prefixes = await res.json();
|
||||||
|
const prefixObj = Array.from(prefixes).find((p) => p.prefix == prefix);
|
||||||
|
return { prefix: prefixObj, prefixes };
|
||||||
|
};
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { afterNavigate, beforeNavigate } from '$app/navigation';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
import { bS, bAS, iS } from '$lib/client/styles';
|
||||||
|
import HeaderBar from '$lib/client/components/HeaderBar.svelte';
|
||||||
|
import PagerBar from '$lib/client/components/PagerBar.svelte';
|
||||||
|
import CommandBar from '$lib/client/components/CommandBar.svelte';
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
let { prefix, prefixes } = $derived(data);
|
||||||
|
|
||||||
|
let pageTitle = $derived(`${prefix.prefix} Baskets | TAM`);
|
||||||
|
|
||||||
|
let curIdx = $state(0),
|
||||||
|
nextIdx = $derived(curIdx + 1),
|
||||||
|
prevIdx = $derived(curIdx - 1);
|
||||||
|
const changeIdx = (idx) => {
|
||||||
|
curIdx = idx;
|
||||||
|
};
|
||||||
|
const focusIdx = (idx) => {
|
||||||
|
curIdx = idx;
|
||||||
|
const elemIdx = document.getElementById(`${idx}_first`);
|
||||||
|
if (elemIdx) {
|
||||||
|
elemIdx.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let pager = $state({ idFrom: 0, idTo: 0 });
|
||||||
|
let items = $state([]);
|
||||||
|
let itemsLength = $derived(items.length);
|
||||||
|
let itemsBuffer = $derived(items.filter((i) => i.changed));
|
||||||
|
const functions = {
|
||||||
|
getPage: async () => {
|
||||||
|
functions.save();
|
||||||
|
if (pager.idFrom > pager.idTo) {
|
||||||
|
[pager.idFrom, pager.idTo] = [pager.idTo, pager.idFrom];
|
||||||
|
}
|
||||||
|
if (pager.idTo - pager.idFrom > 300) {
|
||||||
|
pager.idTo = pager.idFrom + 300;
|
||||||
|
}
|
||||||
|
const res = await fetch(`/api/baskets/${prefix.prefix}/${pager.idFrom}/${pager.idTo}`);
|
||||||
|
const resData = await res.json();
|
||||||
|
resData.map((i) => (i.changed = false));
|
||||||
|
items = [...resData];
|
||||||
|
setTimeout(() => focusIdx(0));
|
||||||
|
},
|
||||||
|
save: async () => {
|
||||||
|
if (itemsBuffer.length > 0) {
|
||||||
|
const res = await fetch('/api/baskets', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(itemsBuffer)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
itemsBuffer.forEach((i) => (i.changed = false));
|
||||||
|
} else {
|
||||||
|
alert('Error saving items.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
focusIdx(0);
|
||||||
|
}, 1);
|
||||||
|
},
|
||||||
|
prevPage: () => {
|
||||||
|
((pager.idFrom -= itemsLength), (pager.idTo -= itemsLength));
|
||||||
|
functions.getPage();
|
||||||
|
},
|
||||||
|
nextPage: () => {
|
||||||
|
((pager.idFrom += itemsLength), (pager.idTo += itemsLength));
|
||||||
|
functions.getPage();
|
||||||
|
},
|
||||||
|
nextLine: () => {
|
||||||
|
if (items[nextIdx]) {
|
||||||
|
setTimeout(() => {
|
||||||
|
focusIdx(nextIdx);
|
||||||
|
}, 1);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
focusIdx(curIdx);
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prevLine: () => {
|
||||||
|
if (items[prevIdx]) {
|
||||||
|
setTimeout(() => {
|
||||||
|
focusIdx(prevIdx);
|
||||||
|
}, 1);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
focusIdx(curIdx);
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dupDown: () => {
|
||||||
|
if (items[nextIdx]) {
|
||||||
|
const buffer = { ...items[curIdx] };
|
||||||
|
['prefix', 't_id'].forEach((key) => delete buffer[key]);
|
||||||
|
items[nextIdx] = { ...items[nextIdx], ...buffer, changed: true };
|
||||||
|
functions.nextLine();
|
||||||
|
} else {
|
||||||
|
focusIdx(curIdx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dupUp: () => {
|
||||||
|
if (curIdx > 0) {
|
||||||
|
const buffer = { ...items[curIdx] };
|
||||||
|
['prefix', 't_id'].forEach((key) => delete buffer[key]);
|
||||||
|
items[prevIdx] = { ...items[prevIdx], ...buffer, changed: true };
|
||||||
|
functions.prevLine();
|
||||||
|
} else {
|
||||||
|
focusIdx(curIdx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
copy: () => {
|
||||||
|
if (items[curIdx]) {
|
||||||
|
const buffer = { ...items[curIdx] };
|
||||||
|
['prefix', 't_id'].forEach((key) => delete buffer[key]);
|
||||||
|
window.localStorage.setItem('tam-ticket', JSON.stringify(buffer));
|
||||||
|
}
|
||||||
|
focusIdx(curIdx);
|
||||||
|
},
|
||||||
|
paste: () => {
|
||||||
|
if (items[curIdx]) {
|
||||||
|
const buffer = JSON.parse(window.localStorage.getItem('tam-ticket'));
|
||||||
|
items[curIdx] = { ...items[curIdx], ...buffer, changed: true };
|
||||||
|
}
|
||||||
|
focusIdx(curIdx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const headers = ['Basket ID', 'Description', 'Donors', 'Save?'];
|
||||||
|
|
||||||
|
beforeNavigate(({ cancel }) => {
|
||||||
|
if (itemsBuffer.length > 0) {
|
||||||
|
if (!confirm('Are you sure you want to leave this page? There are unsaved changes!'))
|
||||||
|
cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterNavigate(() => {
|
||||||
|
items = [];
|
||||||
|
pager = { idFrom: 0, idTo: 0 };
|
||||||
|
curIdx = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
window.addEventListener('beforeunload', (e) => {
|
||||||
|
if (itemsBuffer.length > 0) e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{pageTitle}</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<table class="w-full box-border border-separate p-1">
|
||||||
|
<thead class="sticky top-1 bg-white">
|
||||||
|
<tr>
|
||||||
|
<td colspan="50">
|
||||||
|
<HeaderBar>
|
||||||
|
<div>Baskets:</div>
|
||||||
|
{#each prefixes as p (p.prefix)}
|
||||||
|
<a
|
||||||
|
href={resolve('/baskets/[prefix]', { prefix: p.prefix })}
|
||||||
|
class={prefix.prefix == p.prefix ? bAS[p.color] : bS[p.color]}>{p.prefix}</a
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</HeaderBar>
|
||||||
|
<h1 class="text-xl font-bold p-1">{pageTitle}</h1>
|
||||||
|
<PagerBar {prefix} {functions} bind:pager />
|
||||||
|
<CommandBar {prefix} {functions} /></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
{#each headers as header (header)}
|
||||||
|
<th class="border text-left p-0.5">{header}</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each items as item, idx (item.b_id)}
|
||||||
|
<tr
|
||||||
|
class="focus-within:font-bold"
|
||||||
|
onfocusin={(e) => {
|
||||||
|
changeIdx(idx);
|
||||||
|
e.target.scrollIntoView({ block: 'center' });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td class="p-0.5 border">{item.b_id}</td>
|
||||||
|
<td class="p-0.5 border"
|
||||||
|
><input
|
||||||
|
type="text"
|
||||||
|
class="{iS.normal} w-full"
|
||||||
|
id="{idx}_first"
|
||||||
|
onchangecapture={() => (item.changed = true)}
|
||||||
|
bind:value={item.description}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td class="p-0.5 border"
|
||||||
|
><input
|
||||||
|
type="text"
|
||||||
|
class="{iS.normal} w-full"
|
||||||
|
id="{idx}_second"
|
||||||
|
onchangecapture={() => (item.changed = true)}
|
||||||
|
bind:value={item.donors}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td class="p-0.5 border"
|
||||||
|
><button
|
||||||
|
class={bS[prefix.color]}
|
||||||
|
onclick={() => {
|
||||||
|
item.changed ? (item.changed = false) : (item.changed = true);
|
||||||
|
}}>{item.changed ? 'Yes' : 'No'}</button
|
||||||
|
></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getSettings } from "$lib/server/settings";
|
import { getSettings } from '$lib/server/settings';
|
||||||
|
|
||||||
export const load = () => {
|
export const load = () => {
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
return { settings };
|
return { settings };
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { resolve } from '$app/paths'
|
import { resolve } from '$app/paths';
|
||||||
import { bS, iS, tS } from '$lib/client/styles';
|
import { bS, iS, tS } from '$lib/client/styles';
|
||||||
import HeaderBar from '$lib/client/components/HeaderBar.svelte'
|
import HeaderBar from '$lib/client/components/HeaderBar.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
let settings = $state({});
|
let settings = $state({});
|
||||||
let status = $state({
|
let status = $state({
|
||||||
message: '',
|
message: '',
|
||||||
color: 'green'
|
color: 'green'
|
||||||
})
|
});
|
||||||
|
|
||||||
const pageTitle = 'Settings | TAM';
|
const pageTitle = 'Settings | TAM';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
settings = {...data.settings};
|
settings = { ...data.settings };
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -36,46 +36,57 @@
|
|||||||
<h2 class="text-lg font-bold">Remote Mode:</h2>
|
<h2 class="text-lg font-bold">Remote Mode:</h2>
|
||||||
<div class="flex flex-row gap-1 items-center">
|
<div class="flex flex-row gap-1 items-center">
|
||||||
<div>Remote Server:</div>
|
<div>Remote Server:</div>
|
||||||
<input type="text" class={iS.normal} bind:value={settings.remote_server}>
|
<input type="text" class={iS.normal} bind:value={settings.remote_server} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-1 items-center">
|
<div class="flex flex-row gap-1 items-center">
|
||||||
<div>Remote Port:</div>
|
<div>Remote Port:</div>
|
||||||
<input type="text" class={iS.normal} bind:value={settings.remote_port}>
|
<input type="text" class={iS.normal} bind:value={settings.remote_port} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-1 items-center">
|
<div class="flex flex-row gap-1 items-center">
|
||||||
<div>Remote TLS:</div>
|
<div>Remote TLS:</div>
|
||||||
<button class={bS.gray} onclick={() => {
|
<button
|
||||||
settings.remote_tls = !settings.remote_tls
|
class={bS.gray}
|
||||||
}}>{settings.remote_tls ? 'Yes' : 'No'}</button>
|
onclick={() => {
|
||||||
|
settings.remote_tls = !settings.remote_tls;
|
||||||
|
}}>{settings.remote_tls ? 'Yes' : 'No'}</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-lg font-bold">Default Preferences:</h2>
|
<h2 class="text-lg font-bold">Default Preferences:</h2>
|
||||||
<div class="flex flex-row gap-1 items-center">
|
<div class="flex flex-row gap-1 items-center">
|
||||||
<div>Contact Preference:</div>
|
<div>Contact Preference:</div>
|
||||||
<button class={bS.gray} onclick={() => {
|
<button
|
||||||
settings.default_pref === 'CALL' ? settings.default_pref = 'TEXT' : settings.default_pref = 'CALL';
|
class={bS.gray}
|
||||||
}}>{settings.default_pref}</button>
|
onclick={() => {
|
||||||
|
settings.default_pref === 'CALL'
|
||||||
|
? (settings.default_pref = 'TEXT')
|
||||||
|
: (settings.default_pref = 'CALL');
|
||||||
|
}}>{settings.default_pref}</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-1 items-center">
|
<div class="flex flex-row gap-1 items-center">
|
||||||
<div>Venue Name:</div>
|
<div>Venue Name:</div>
|
||||||
<input type="text" class={iS.normal} bind:value={settings.venue_name}>
|
<input type="text" class={iS.normal} bind:value={settings.venue_name} />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-1 items-center">
|
<div class="flex flex-row gap-1 items-center">
|
||||||
<button class={bS.gray} onclick={async () => {
|
<button
|
||||||
|
class={bS.gray}
|
||||||
|
onclick={async () => {
|
||||||
const res = await fetch('/api/settings', {
|
const res = await fetch('/api/settings', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(settings),
|
body: JSON.stringify(settings),
|
||||||
headers: {'Content-Type': 'application/json'}
|
headers: { 'Content-Type': 'application/json' }
|
||||||
})
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
status.message = `Error Code: ${res.status}`;
|
status.message = `Error Code: ${res.status}`;
|
||||||
status.color = 'red';
|
status.color = 'red';
|
||||||
} else {
|
} else {
|
||||||
const resData = await res.json();
|
const resData = await res.json();
|
||||||
settings = {...resData};
|
settings = { ...resData };
|
||||||
status.message = 'Settings saved successfully!';
|
status.message = 'Settings saved successfully!';
|
||||||
status.color = 'green';
|
status.color = 'green';
|
||||||
}
|
}
|
||||||
}}>Save</button>
|
}}>Save</button
|
||||||
|
>
|
||||||
<button class={bS.gray}>Cancel</button>
|
<button class={bS.gray}>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getSettings } from "$lib/server/settings";
|
import { getSettings } from '$lib/server/settings';
|
||||||
|
|
||||||
export const load = () => {
|
export const load = () => {
|
||||||
const s = getSettings();
|
const s = getSettings();
|
||||||
return { authKey: s.remote_key };
|
return { authKey: s.remote_key };
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -92,9 +92,9 @@
|
|||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const newKeys = authKeys.filter(i => i.auth_key !== key.auth_key);
|
const newKeys = authKeys.filter((i) => i.auth_key !== key.auth_key);
|
||||||
authKeys = [...newKeys];
|
authKeys = [...newKeys];
|
||||||
}, 1)
|
}, 1);
|
||||||
}
|
}
|
||||||
}}>Delete</button
|
}}>Delete</button
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ export const load = async ({ fetch }) => {
|
|||||||
const res = await fetch('/api/prefixes');
|
const res = await fetch('/api/prefixes');
|
||||||
const prefixes = await res.json();
|
const prefixes = await res.json();
|
||||||
return { prefixes };
|
return { prefixes };
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ export const load = async ({ params, fetch }) => {
|
|||||||
const { prefix } = params;
|
const { prefix } = params;
|
||||||
const res = await fetch('/api/prefixes');
|
const res = await fetch('/api/prefixes');
|
||||||
const prefixes = await res.json();
|
const prefixes = await res.json();
|
||||||
const prefixObj = Array.from(prefixes).find(p => p.prefix == prefix);
|
const prefixObj = Array.from(prefixes).find((p) => p.prefix == prefix);
|
||||||
return { prefix: prefixObj, prefixes};
|
return { prefix: prefixObj, prefixes };
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
focusIdx(curIdx);
|
focusIdx(curIdx);
|
||||||
}, 1)
|
}, 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prevLine: () => {
|
prevLine: () => {
|
||||||
@@ -88,15 +88,15 @@
|
|||||||
}, 1);
|
}, 1);
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
focusIdx(curIdx)
|
focusIdx(curIdx);
|
||||||
}, 1);
|
}, 1);
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
dupDown: () => {
|
dupDown: () => {
|
||||||
if (items[nextIdx]) {
|
if (items[nextIdx]) {
|
||||||
const buffer = {...items[curIdx]};
|
const buffer = { ...items[curIdx] };
|
||||||
["prefix", "t_id"].forEach(key => delete buffer[key]);
|
['prefix', 't_id'].forEach((key) => delete buffer[key]);
|
||||||
items[nextIdx] = {...items[nextIdx], ...buffer, changed: true};
|
items[nextIdx] = { ...items[nextIdx], ...buffer, changed: true };
|
||||||
functions.nextLine();
|
functions.nextLine();
|
||||||
} else {
|
} else {
|
||||||
focusIdx(curIdx);
|
focusIdx(curIdx);
|
||||||
@@ -104,9 +104,9 @@
|
|||||||
},
|
},
|
||||||
dupUp: () => {
|
dupUp: () => {
|
||||||
if (curIdx > 0) {
|
if (curIdx > 0) {
|
||||||
const buffer = {...items[curIdx]};
|
const buffer = { ...items[curIdx] };
|
||||||
["prefix", "t_id"].forEach(key => delete buffer[key]);
|
['prefix', 't_id'].forEach((key) => delete buffer[key]);
|
||||||
items[prevIdx] = {...items[prevIdx], ...buffer, changed: true};
|
items[prevIdx] = { ...items[prevIdx], ...buffer, changed: true };
|
||||||
functions.prevLine();
|
functions.prevLine();
|
||||||
} else {
|
} else {
|
||||||
focusIdx(curIdx);
|
focusIdx(curIdx);
|
||||||
@@ -114,17 +114,17 @@
|
|||||||
},
|
},
|
||||||
copy: () => {
|
copy: () => {
|
||||||
if (items[curIdx]) {
|
if (items[curIdx]) {
|
||||||
const buffer = {...items[curIdx]};
|
const buffer = { ...items[curIdx] };
|
||||||
["prefix", "t_id"].forEach(key => delete buffer[key]);
|
['prefix', 't_id'].forEach((key) => delete buffer[key]);
|
||||||
window.localStorage.setItem('tam-ticket', JSON.stringify(buffer));
|
window.localStorage.setItem('tam-ticket', JSON.stringify(buffer));
|
||||||
};
|
}
|
||||||
focusIdx(curIdx);
|
focusIdx(curIdx);
|
||||||
},
|
},
|
||||||
paste: () => {
|
paste: () => {
|
||||||
if (items[curIdx]) {
|
if (items[curIdx]) {
|
||||||
const buffer = JSON.parse(window.localStorage.getItem('tam-ticket'));
|
const buffer = JSON.parse(window.localStorage.getItem('tam-ticket'));
|
||||||
items[curIdx] = {...items[curIdx], ...buffer, changed: true};
|
items[curIdx] = { ...items[curIdx], ...buffer, changed: true };
|
||||||
};
|
}
|
||||||
focusIdx(curIdx);
|
focusIdx(curIdx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -154,8 +154,8 @@
|
|||||||
<title>{pageTitle}</title>
|
<title>{pageTitle}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<table class="w-full box-border border-separate">
|
<table class="w-full box-border border-separate p-1">
|
||||||
<thead class="sticky top-1 box-border bg-white">
|
<thead class="sticky top-1 bg-white">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="50">
|
<td colspan="50">
|
||||||
<HeaderBar>
|
<HeaderBar>
|
||||||
@@ -167,16 +167,10 @@
|
|||||||
>
|
>
|
||||||
{/each}
|
{/each}
|
||||||
</HeaderBar>
|
</HeaderBar>
|
||||||
</td>
|
<h1 class="text-xl font-bold p-1">{pageTitle}</h1>
|
||||||
</tr>
|
<PagerBar {prefix} {functions} bind:pager />
|
||||||
<tr>
|
<CommandBar {prefix} {functions} /></td
|
||||||
<td colspan="50"><h1 class="text-xl font-bold p-1">{pageTitle}</h1></td>
|
>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="50"><PagerBar {prefix} {functions} bind:pager /></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td colspan="50"><CommandBar {prefix} {functions} /></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
{#each headers as header (header)}
|
{#each headers as header (header)}
|
||||||
@@ -207,6 +201,7 @@
|
|||||||
><input
|
><input
|
||||||
type="text"
|
type="text"
|
||||||
class="{iS.normal} w-full"
|
class="{iS.normal} w-full"
|
||||||
|
id="{idx}_second"
|
||||||
onchangecapture={() => (item.changed = true)}
|
onchangecapture={() => (item.changed = true)}
|
||||||
bind:value={item.last_name}
|
bind:value={item.last_name}
|
||||||
/></td
|
/></td
|
||||||
@@ -215,6 +210,7 @@
|
|||||||
><input
|
><input
|
||||||
type="text"
|
type="text"
|
||||||
class="{iS.normal} w-full"
|
class="{iS.normal} w-full"
|
||||||
|
id="{idx}_third"
|
||||||
onchangecapture={() => (item.changed = true)}
|
onchangecapture={() => (item.changed = true)}
|
||||||
bind:value={item.phone_number}
|
bind:value={item.phone_number}
|
||||||
/></td
|
/></td
|
||||||
@@ -225,6 +221,15 @@
|
|||||||
onclick={() => {
|
onclick={() => {
|
||||||
item.pref == 'CALL' ? (item.pref = 'TEXT') : (item.pref = 'CALL');
|
item.pref == 'CALL' ? (item.pref = 'TEXT') : (item.pref = 'CALL');
|
||||||
item.changed = true;
|
item.changed = true;
|
||||||
|
}}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key == 't') {
|
||||||
|
if (item.pref != 'TEXT') item.changed = true;
|
||||||
|
item.pref = 'TEXT';
|
||||||
|
} else if (e.key == 'c') {
|
||||||
|
if (item.pref != 'CALL') item.changed = true;
|
||||||
|
item.pref = 'CALL';
|
||||||
|
}
|
||||||
}}>{item.pref}</button
|
}}>{item.pref}</button
|
||||||
></td
|
></td
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user