diff --git a/api/app/data/search.py b/api/app/data/search.py
new file mode 100644
index 0000000..f6ad5fe
--- /dev/null
+++ b/api/app/data/search.py
@@ -0,0 +1,37 @@
+from fastapi import APIRouter, Header
+from .tickets import Ticket
+from db import RepoTemplate
+from system.auth import AuthRepo
+
+chunk_size = 300
+
+def wild_enc(str_in: str):
+ return f"%{str_in}%"
+
+class SearchRepo(RepoTemplate):
+ def ticket_search(self, first_name: str, last_name: str, phone_number: str):
+ self.cur.execute("SELECT * FROM tickets WHERE first_name LIKE ? AND last_name LIKE ? AND phone_number LIKE ? ORDER BY prefix, t_id", (wild_enc(first_name), wild_enc(last_name), wild_enc(phone_number)))
+ results = self.cur.fetchall()
+ return [Ticket(*r) for r in results]
+ def post_tickets(self, ts: list[Ticket]):
+ for i in range(0, len(ts), chunk_size):
+ chunk = ts[i:i+chunk_size]
+ for t in chunk:
+ 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
+
+search_router = APIRouter(prefix="/api/search")
+
+@search_router.get("/tickets")
+def search_tickets(first_name: str = "", last_name: str = "", phone_number: str = "", tam_key: str = Header("")):
+ AuthRepo().verify_key(tam_key)
+ return SearchRepo().ticket_search(first_name, last_name, phone_number)
+
+@search_router.post("/tickets")
+def post_tickets(ts: list[Ticket], tam_key: str = Header("")):
+ AuthRepo().verify_key(tam_key)
+ return SearchRepo().post_tickets(ts)
diff --git a/api/app/routers.py b/api/app/routers.py
index 25806fe..52176af 100644
--- a/api/app/routers.py
+++ b/api/app/routers.py
@@ -5,6 +5,7 @@ from data.tickets import tickets_router
from data.baskets import basket_router
from data.drawing import drawing_router
from data.reports import reports_router
+from data.search import search_router
def imp_routers(app: FastAPI):
app.include_router(auth_router)
@@ -13,3 +14,4 @@ def imp_routers(app: FastAPI):
app.include_router(basket_router)
app.include_router(drawing_router)
app.include_router(reports_router)
+ app.include_router(search_router)
diff --git a/client/.env.example b/client/.env.example
index b86d245..6142df5 100644
--- a/client/.env.example
+++ b/client/.env.example
@@ -1,2 +1 @@
-# Drizzle
-DATABASE_URL=local.db
+TAM_DATA_DIR=./data
diff --git a/client/drizzle/0000_init.sql b/client/drizzle/0000_init.sql
index f3fa0c3..759894b 100644
--- a/client/drizzle/0000_init.sql
+++ b/client/drizzle/0000_init.sql
@@ -1,4 +1,4 @@
-CREATE TABLE `baskets` (
+CREATE TABLE IF NOT EXISTS `baskets` (
`prefix` text,
`b_id` integer,
`description` text,
@@ -7,13 +7,13 @@ CREATE TABLE `baskets` (
PRIMARY KEY(`prefix`, `b_id`)
);
--> statement-breakpoint
-CREATE TABLE `prefixes` (
+CREATE TABLE IF NOT EXISTS `prefixes` (
`prefix` text PRIMARY KEY NOT NULL,
`color` text,
`weight` integer
);
--> statement-breakpoint
-CREATE TABLE `tickets` (
+CREATE TABLE IF NOT EXISTS `tickets` (
`prefix` text,
`t_id` integer,
`first_name` text,
@@ -23,18 +23,18 @@ CREATE TABLE `tickets` (
PRIMARY KEY(`prefix`, `t_id`)
);
--> statement-breakpoint
-CREATE VIEW `drawing` AS SELECT b.prefix, b.b_id, b.description, b.winning_ticket, t.last_name, t.first_name, t.phone_number
+CREATE VIEW IF NOT EXISTS `drawing` AS 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
ORDER BY b.prefix, b.b_id;--> statement-breakpoint
-CREATE VIEW `report_by_basket` AS SELECT b.prefix, b.b_id, b.description, b.donors, b.winning_ticket, t.last_name, t.first_name, t.phone_number, t.pref
+CREATE VIEW IF NOT EXISTS `report_by_basket` AS 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
ORDER BY b.prefix, b.b_id;--> statement-breakpoint
-CREATE VIEW `report_by_name` AS SELECT t.last_name, t.first_name, t.phone_number, t.pref, b.prefix, b.b_id, b.description, b.donors, b.winning_ticket
+CREATE VIEW IF NOT EXISTS `report_by_name` AS 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
ORDER BY t.last_name, t.first_name, t.phone_number, b.prefix, b.b_id;--> statement-breakpoint
-CREATE VIEW `report_counts` AS SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys
+CREATE VIEW IF NOT EXISTS `report_counts` AS SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys
FROM tickets
GROUP BY prefix
UNION ALL
SELECT 'Total', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*)
- FROM tickets;
\ No newline at end of file
+ FROM tickets;
diff --git a/client/src/hooks.server.js b/client/src/hooks.server.js
new file mode 100644
index 0000000..c1380b2
--- /dev/null
+++ b/client/src/hooks.server.js
@@ -0,0 +1,41 @@
+import { db } from "$lib/server/db";
+
+export const init = async () => {
+ await db.run(`CREATE TABLE IF NOT EXISTS baskets (
+ prefix text,
+ b_id integer,
+ description text,
+ donors text,
+ winning_ticket integer,
+ PRIMARY KEY(prefix, b_id)
+ )`);
+ await db.run(`CREATE TABLE IF NOT EXISTS prefixes (
+ prefix text PRIMARY KEY NOT NULL,
+ color text,
+ weight integer
+ )`);
+ await db.run(`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)
+ )`);
+ await db.run(`CREATE VIEW IF NOT EXISTS drawing AS 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
+ ORDER BY b.prefix, b.b_id`);
+ await db.run(`CREATE VIEW IF NOT EXISTS report_by_basket AS 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
+ ORDER BY b.prefix, b.b_id`);
+ await db.run(`CREATE VIEW IF NOT EXISTS report_by_name AS 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
+ ORDER BY t.last_name, t.first_name, t.phone_number, b.prefix, b.b_id`)
+ await db.run(`CREATE VIEW IF NOT EXISTS report_counts AS SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys
+ FROM tickets
+ GROUP BY prefix
+ UNION ALL
+ SELECT 'Total', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*)
+ FROM tickets`);
+}
diff --git a/client/src/lib/client/components/TicketSearchBar.svelte b/client/src/lib/client/components/TicketSearchBar.svelte
new file mode 100644
index 0000000..907d753
--- /dev/null
+++ b/client/src/lib/client/components/TicketSearchBar.svelte
@@ -0,0 +1,60 @@
+
+
+
+
+
First Name:
+
e.target.select()}
+ />
+
Last Name:
+
e.target.select()}
+ />
+
Phone Number:
+
e.target.select()}
+ />
+ {#if functions.search}
+
+ {/if}
+
+
diff --git a/client/src/lib/client/styles.js b/client/src/lib/client/styles.js
index 4296ce2..4bbe64d 100644
--- a/client/src/lib/client/styles.js
+++ b/client/src/lib/client/styles.js
@@ -1,12 +1,12 @@
export const tS = {
- gray: 'text-gray-700 font-bold',
- white: 'font-bold',
- blue: 'text-blue-700 font-bold',
- yellow: 'text-yellow-600 font-bold',
- green: 'text-green-700 font-bold',
- orange: 'text-orange-700 font-bold',
- purple: 'text-purple-700 font-bold',
- red: 'text-red-700 font-bold'
+ gray: 'text-gray-700',
+ white: 'text-black',
+ blue: 'text-blue-700',
+ yellow: 'text-yellow-600',
+ green: 'text-green-700',
+ orange: 'text-orange-700',
+ purple: 'text-purple-700',
+ red: 'text-red-700'
};
export const bS = {
diff --git a/client/src/lib/server/db/schema.js b/client/src/lib/server/db/schema.js
index 88f762b..345745c 100644
--- a/client/src/lib/server/db/schema.js
+++ b/client/src/lib/server/db/schema.js
@@ -76,12 +76,13 @@ export const reportByBasket = sqliteView('report_by_basket', {
ORDER BY b.prefix, b.b_id`);
export const reportCounts = sqliteView('report_counts', {
- prefix: text('prefix'),
- unique_buyers: integer('unique_buyers'),
- total_buys: integer('total_buys')
-}).as(sql`SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys
+ prefix: text('prefix'),
+ unique_buyers: integer('unique_buyers'),
+ total_buys: integer('total_buys')
+})
+ .as(sql`SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys
FROM tickets
GROUP BY prefix
UNION ALL
SELECT 'Total', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*)
- FROM tickets`)
+ FROM tickets`);
diff --git a/client/src/routes/+page.svelte b/client/src/routes/+page.svelte
index f7012bb..348cfe8 100644
--- a/client/src/routes/+page.svelte
+++ b/client/src/routes/+page.svelte
@@ -1,5 +1,5 @@
- {pageTitle}
+ {pageTitle}
-
-
-
{pageTitle}
-
-
-
- | Prefix |
- Unique Buyers |
- Total Buys |
-
-
-
- {#each tableData as line (line.prefix)}
-
- | {line.prefix} |
- {line.unique_buyers || 0} |
- {line.total_buys || 0} |
-
- {/each}
-
-
-
-
-
-
Last refreshed: {lastRefreshed}
-
+
+
{pageTitle}
+
+
+
+ | Prefix |
+ Unique Buyers |
+ Total Buys |
+
+
+
+ {#each tableData as line (line.prefix)}
+
+ | {line.prefix} |
+ {line.unique_buyers || 0} |
+ {line.total_buys || 0} |
+
+ {/each}
+
+
+
+
+
+
Last refreshed: {lastRefreshed}
+
diff --git a/client/src/routes/search/tickets/+page.server.js b/client/src/routes/search/tickets/+page.server.js
new file mode 100644
index 0000000..2a0e806
--- /dev/null
+++ b/client/src/routes/search/tickets/+page.server.js
@@ -0,0 +1,10 @@
+export const load = async ({ fetch }) => {
+ const prefix = {
+ prefix: '',
+ color: 'gray',
+ weight: 0
+ };
+ const res = await fetch('/api/prefixes');
+ const prefixes = await res.json();
+ return { prefix, prefixes };
+};
diff --git a/client/src/routes/search/tickets/+page.svelte b/client/src/routes/search/tickets/+page.svelte
new file mode 100644
index 0000000..5888cae
--- /dev/null
+++ b/client/src/routes/search/tickets/+page.svelte
@@ -0,0 +1,233 @@
+
+
+
+ {pageTitle}
+
+
+
diff --git a/client/src/routes/settings/+page.svelte b/client/src/routes/settings/+page.svelte
index cf438e0..f637778 100644
--- a/client/src/routes/settings/+page.svelte
+++ b/client/src/routes/settings/+page.svelte
@@ -23,30 +23,36 @@
-
- Settings Sections:
- {#if data.settings.remote_server}
- Auth Keys
- {/if}
- Prefixes
-
+
+ Settings Sections:
+ {#if data.settings.remote_server}
+ Auth Keys
+ {/if}
+ Prefixes
+
{pageTitle}
Remote Mode:
Remote TLS:
@@ -64,7 +70,7 @@
-
+
{status.message}
diff --git a/client/src/routes/settings/auth-keys/+page.svelte b/client/src/routes/settings/auth-keys/+page.svelte
index d8a90ed..d77520a 100644
--- a/client/src/routes/settings/auth-keys/+page.svelte
+++ b/client/src/routes/settings/auth-keys/+page.svelte
@@ -14,6 +14,21 @@
let authKeys = $state([]);
let newDesc = $state('');
let curKey = $state('');
+
+ async function getKeys() {
+ const res = await fetch('/api/auth', {
+ headers: { 'TAM-PWD': auth.pwd }
+ });
+ if (res.ok) {
+ auth.toggle = true;
+ const resData = await res.json();
+ authKeys = [...resData];
+ curKey = data.authKey;
+ } else {
+ auth.pwd = 'Invalid Password!';
+ setTimeout(() => (auth.pwd = ''), 3000);
+ }
+ }
@@ -21,33 +36,17 @@
-
- Back to Settings
- Other Settings:
- Prefixes
-
+
+ Back to Settings
+ Other Settings:
+ Prefixes
+
{pageTitle}
{#if !auth.toggle}
{:else}
@@ -103,7 +102,9 @@
{/each}
| New |
- |
+ |
+ import { bS, iS } from '$lib/client/styles';
+ import HeaderBar from '$lib/client/components/HeaderBar.svelte';
+
+ const pageTitle = 'Print Ticket Sheets | TAM';
+
+ let inputs = $state({ start: 0, end: 0, perPage: 20, ticketType: '' });
+
+ let items = $state([]);
+
+ function runSheet() {
+ items = [];
+ for (let i = inputs.start; i <= inputs.end; i++) items = [...items, i];
+ }
+
+ function resetSheet() {
+ items = [];
+ }
+
+
+
+ {pageTitle}
+
+
+
+
+
+ |
+
+ {pageTitle}
+
+ |
+
+
+
+
+ Range:
+
+ -
+
+ Per Page:
+
+ Ticket Type:
+
+
+
+
+ |
+
+ {#if inputs.ticketType}
+
+ {inputs.ticketType} Ticket Sales |
+
+ {/if}
+
+ | Ticket # |
+ Name |
+ Phone Number |
+ Text? |
+
+
+
+ {#each items as i, idx (i)}
+
+ | {i} |
+ |
+ |
+ |
+
+ {:else}
+
+ | No rows created. Please use the form above to create rows. |
+
+ {/each}
+
+
diff --git a/client/src/routes/tickets/[prefix]/+page.svelte b/client/src/routes/tickets/[prefix]/+page.svelte
index c8e5d8f..7685621 100644
--- a/client/src/routes/tickets/[prefix]/+page.svelte
+++ b/client/src/routes/tickets/[prefix]/+page.svelte
@@ -149,8 +149,7 @@
{#each prefixes as p (p.prefix)}
{p.prefix}{p.prefix}
{/each}
@@ -231,11 +230,12 @@
>
|
{:else}
-
- |
- No rows loaded. Please use the pager at the top to put in the first, then last number on the sheet, click Go, and that should load in the sheet.
- |
-
+
+ |
+ No rows loaded. Please use the pager at the top to put in the first, then last number on
+ the sheet, click Go, and that should load in the sheet.
+ |
+
{/each}