From e5b6fbd7c52b49f8b7414918d3830680eba4dbe8 Mon Sep 17 00:00:00 2001 From: Dilan Gilluly Date: Tue, 23 Jun 2026 18:26:31 -0400 Subject: [PATCH] (add): Tickets Logic --- api/.gitignore | 2 +- .../data/__pycache__/tickets.cpython-314.pyc | Bin 0 -> 7575 bytes api/app/data/tickets.py | 65 +++++ api/app/db.py | 2 + api/app/routers.py | 2 + .../lib/client/components/CommandBar.svelte | 67 +++++ .../src/lib/client/components/PagerBar.svelte | 79 ++++++ client/src/lib/client/styles.js | 10 + client/src/lib/server/db/schema.js | 11 +- client/src/routes/+page.server.js | 4 +- client/src/routes/+page.svelte | 34 ++- client/src/routes/api/prefixes/+server.js | 16 +- client/src/routes/api/tickets/+server.js | 52 ++++ .../routes/api/tickets/[prefix]/+server.js | 26 ++ .../[prefix]/[id_from]/[id_to]/+server.js | 39 +++ .../api/tickets/[prefix]/[t_id]/+server.js | 59 +++++ .../routes/settings/auth-keys/+page.svelte | 1 + .../src/routes/settings/prefixes/+page.svelte | 213 ++++++++------- .../routes/tickets/[prefix]/+page.server.js | 7 + .../src/routes/tickets/[prefix]/+page.svelte | 242 ++++++++++++++++++ 20 files changed, 827 insertions(+), 104 deletions(-) create mode 100644 api/app/data/__pycache__/tickets.cpython-314.pyc create mode 100644 api/app/data/tickets.py create mode 100644 client/src/lib/client/components/CommandBar.svelte create mode 100644 client/src/lib/client/components/PagerBar.svelte create mode 100644 client/src/routes/api/tickets/+server.js create mode 100644 client/src/routes/api/tickets/[prefix]/+server.js create mode 100644 client/src/routes/api/tickets/[prefix]/[id_from]/[id_to]/+server.js create mode 100644 client/src/routes/api/tickets/[prefix]/[t_id]/+server.js create mode 100644 client/src/routes/tickets/[prefix]/+page.server.js create mode 100644 client/src/routes/tickets/[prefix]/+page.svelte diff --git a/api/.gitignore b/api/.gitignore index 76d3e41..6ca7b68 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -1,2 +1,2 @@ -data +/data */__pycache__ diff --git a/api/app/data/__pycache__/tickets.cpython-314.pyc b/api/app/data/__pycache__/tickets.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e9e5910206ef8d403f47b1910a577d066ba6f79 GIT binary patch literal 7575 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 literal 0 HcmV?d00001 diff --git a/api/app/data/tickets.py b/api/app/data/tickets.py new file mode 100644 index 0000000..c45094a --- /dev/null +++ b/api/app/data/tickets.py @@ -0,0 +1,65 @@ +from dataclasses import dataclass +from db import RepoTemplate +from system.auth import AuthRepo +from fastapi import APIRouter, Header + +@dataclass +class Ticket: + prefix: str + t_id: int + first_name: str = "" + last_name: str = "" + phone_number: str = "" + pref: str = "" + +class TicketRepo(RepoTemplate): + def get_all_tickets(self): + self.cur.execute("SELECT * FROM tickets ORDER BY prefix, t_id") + results = self.cur.fetchall() + return [Ticket(*r) for r in results] + def get_prefix_tickets(self, prefix: str): + self.cur.execute("SELECT * FROM tickets WHERE prefix = ? ORDER BY prefix, t_id", (prefix,)) + results = self.cur.fetchall() + return [Ticket(*r) for r in results] + def get_single_ticket(self, prefix: str, id: int): + self.cur.execute("SELECT * FROM tickets WHERE prefix = ? AND t_id = ? ORDER BY prefix, t_id", (prefix, id)) + results = self.cur.fetchall() + return [Ticket(*r) for r in results] + def get_range_tickets(self, prefix: str, id_from: int, id_to: int): + self.cur.execute("SELECT * FROM tickets WHERE prefix = ? AND t_id BETWEEN ? AND ? ORDER BY prefix, t_id", (prefix, id_from, id_to)) + results = self.cur.fetchall() + return [Ticket(*r) for r in results] + def post_tickets(self, ts: list[Ticket]): + for t in ts: + self.cur.execute("""INSERT INTO tickets VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (prefix, t_id) DO UPDATE SET + first_name = EXCLUDED.first_name, last_name = EXCLUDED.last_name, phone_number = EXCLUDED.phone_number, + pref = EXCLUDED.pref""", (t.prefix, t.t_id, t.first_name, t.last_name, t.phone_number, t.pref)) + self.conn.commit() + return ts + +tickets_router = APIRouter(prefix="/api/tickets") + +@tickets_router.get("") +def get_all_tickets(tam_key: str = Header("")): + AuthRepo().verify_key(tam_key) + return TicketRepo().get_all_tickets() + +@tickets_router.get("/{prefix}") +def get_prefix_tickets(prefix: str, tam_key: str = Header("")): + AuthRepo().verify_key(tam_key) + return TicketRepo().get_prefix_tickets(prefix) + +@tickets_router.get("/{prefix}/{t_id}") +def get_single_ticket(prefix: str, t_id: int, tam_key: str = Header("")): + AuthRepo().verify_key(tam_key) + return TicketRepo().get_single_ticket(prefix, t_id) + +@tickets_router.get("/{prefix}/{id_from}/{id_to}") +def get_range_tickets(prefix: str, id_from: int, id_to: int, tam_key: str = Header("")): + AuthRepo().verify_key(tam_key) + return TicketRepo().get_range_tickets(prefix, id_from, id_to) + +@tickets_router.post("") +def post_tickets(ts: list[Ticket], tam_key: str = Header("")): + AuthRepo().verify_key(tam_key) + return TicketRepo().post_tickets(ts) diff --git a/api/app/db.py b/api/app/db.py index 24ab2b4..3e1c02b 100644 --- a/api/app/db.py +++ b/api/app/db.py @@ -17,6 +17,8 @@ def init_db(): conn, cur = session() cur.execute("CREATE TABLE IF NOT EXISTS auth_keys (auth_key TEXT PRIMARY KEY, description TEXT)") 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, + phone_number TEXT, pref TEXT, PRIMARY KEY (prefix, t_id))""") conn.commit() conn.close() diff --git a/api/app/routers.py b/api/app/routers.py index d598ee0..e008d6a 100644 --- a/api/app/routers.py +++ b/api/app/routers.py @@ -1,7 +1,9 @@ from fastapi import FastAPI from system.auth import auth_router from system.prefixes import prefix_router +from data.tickets import tickets_router def imp_routers(app: FastAPI): app.include_router(auth_router) app.include_router(prefix_router) + app.include_router(tickets_router) diff --git a/client/src/lib/client/components/CommandBar.svelte b/client/src/lib/client/components/CommandBar.svelte new file mode 100644 index 0000000..c94cae9 --- /dev/null +++ b/client/src/lib/client/components/CommandBar.svelte @@ -0,0 +1,67 @@ + + +
+
+ {#if functions.dupDown} + + {/if} + {#if functions.dupUp} + + {/if} + {#if functions.nextLine} + + {/if} + {#if functions.prevLine} + + {/if} + {#if functions.copy} + + {/if} + {#if functions.paste} + + {/if} +
+
+ {#if functions.save} + + {/if} +
+
diff --git a/client/src/lib/client/components/PagerBar.svelte b/client/src/lib/client/components/PagerBar.svelte new file mode 100644 index 0000000..e6c200d --- /dev/null +++ b/client/src/lib/client/components/PagerBar.svelte @@ -0,0 +1,79 @@ + + +
+
+ { + e.target.select(); + }} + bind:value={pager.idFrom} + /> +
-
+ { + e.target.select(); + }} + bind:value={pager.idTo} + /> + +
+
+ {#if functions.prevPage} + + {/if} + {#if functions.nextPage} + + {/if} +
+
diff --git a/client/src/lib/client/styles.js b/client/src/lib/client/styles.js index 88ea2f8..980f9e3 100644 --- a/client/src/lib/client/styles.js +++ b/client/src/lib/client/styles.js @@ -14,6 +14,16 @@ export const bS = { red: 'px-2 py-1 bg-red-300 border border-gray-700 rounded hover:bg-red-400 cursor-pointer' } +export const bAS = { + 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', + 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', + 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' +} + export const iS = { normal: 'p-1 border border-black' } diff --git a/client/src/lib/server/db/schema.js b/client/src/lib/server/db/schema.js index fba2913..7799e71 100644 --- a/client/src/lib/server/db/schema.js +++ b/client/src/lib/server/db/schema.js @@ -1,7 +1,16 @@ -import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; +import { integer, sqliteTable, text, primaryKey } from 'drizzle-orm/sqlite-core'; export const prefixes = sqliteTable('prefixes', { prefix: text('prefix').primaryKey(), color: text('color'), weight: integer('weight') }); + +export const tickets = sqliteTable('tickets', { + prefix: text('prefix'), + t_id: integer('t_id'), + first_name: text('first_name'), + last_name: text('last_name'), + phone_number: text('phone_number'), + pref: text('pref') +}, (t) => [primaryKey({columns: [t.prefix, t.t_id]})]) diff --git a/client/src/routes/+page.server.js b/client/src/routes/+page.server.js index a8ded64..26ea848 100644 --- a/client/src/routes/+page.server.js +++ b/client/src/routes/+page.server.js @@ -4,5 +4,7 @@ export const load = async ({ fetch }) => { const settings = getSettings(); const res = await fetch('/api'); const data = await res.json(); - return { ...data, venueName: settings.venue_name }; + const resPrefixes = await fetch('/api/prefixes'); + const dataPrefixes = await resPrefixes.json(); + return { ...data, venueName: settings.venue_name, prefixes: dataPrefixes }; }; diff --git a/client/src/routes/+page.svelte b/client/src/routes/+page.svelte index 3fdc921..f0c53d2 100644 --- a/client/src/routes/+page.svelte +++ b/client/src/routes/+page.svelte @@ -1,5 +1,5 @@ - {pageTitle} + {pageTitle} - - Back to Settings - + + Back to Settings +
-

{pageTitle}

-
-
-
Prefix
- -
-
-
Color
- -
-
-
Weight
- -
-
-
Actions
- -
-
-
-
Colors:
- - - - - - -
- - - - - - - - - - - {#each prefixes as prefix (prefix.prefix)} - - - - - - - {/each} - -
PrefixColorWeightActions
{prefix.prefix}{prefix.color.charAt(0).toUpperCase() + prefix.color.slice(1)}{prefix.weight} -
- - -
-
+

{pageTitle}

+
+
+
Prefix
+ +
+
+
Color
+ +
+
+
Weight
+ +
+
+
Actions
+ +
+
+
+
Colors:
+ + + + + + +
+ + + + + + + + + + + {#each prefixes as prefix (prefix.prefix)} + + + + + + + {/each} + +
PrefixColorWeightActions
{prefix.prefix}{prefix.color.charAt(0).toUpperCase() + prefix.color.slice(1)}{prefix.weight} +
+ + +
+
diff --git a/client/src/routes/tickets/[prefix]/+page.server.js b/client/src/routes/tickets/[prefix]/+page.server.js new file mode 100644 index 0000000..77039e4 --- /dev/null +++ b/client/src/routes/tickets/[prefix]/+page.server.js @@ -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}; +} diff --git a/client/src/routes/tickets/[prefix]/+page.svelte b/client/src/routes/tickets/[prefix]/+page.svelte new file mode 100644 index 0000000..e407636 --- /dev/null +++ b/client/src/routes/tickets/[prefix]/+page.svelte @@ -0,0 +1,242 @@ + + + + {pageTitle} + + + + + + + + + + + + + + + + + + {#each headers as header (header)} + + {/each} + + + + {#each items as item, idx (item.t_id)} + { + changeIdx(idx); + e.target.scrollIntoView({ block: 'center' }); + }} + > + + + + + + + + {/each} + +
+ +
Tickets:
+ {#each prefixes as p (p.prefix)} + {p.prefix} + {/each} +
+

{pageTitle}

{header}
{item.t_id} (item.changed = true)} + bind:value={item.first_name} + /> (item.changed = true)} + bind:value={item.last_name} + /> (item.changed = true)} + bind:value={item.phone_number} + />