counts and dockerfiles
This commit is contained in:
2
api/.dockerignore
Normal file
2
api/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__/
|
||||||
|
*/__pycache__/
|
||||||
8
api/Dockerfile
Normal file
8
api/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
FROM python:3
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["fastapi", "run", "main.py"]
|
||||||
@@ -9,6 +9,7 @@ from routers.baskets import basket_router
|
|||||||
from routers.combined import combined_router
|
from routers.combined import combined_router
|
||||||
from routers.reports import report_router
|
from routers.reports import report_router
|
||||||
from routers.backuprestore import backup_router
|
from routers.backuprestore import backup_router
|
||||||
|
from routers.counts import counts_router
|
||||||
|
|
||||||
if argv[1] == "run":
|
if argv[1] == "run":
|
||||||
app = FastAPI(title="TAM3 API Server", docs_url=None, redoc_url=None)
|
app = FastAPI(title="TAM3 API Server", docs_url=None, redoc_url=None)
|
||||||
@@ -21,3 +22,4 @@ app.include_router(basket_router)
|
|||||||
app.include_router(combined_router)
|
app.include_router(combined_router)
|
||||||
app.include_router(report_router)
|
app.include_router(report_router)
|
||||||
app.include_router(backup_router)
|
app.include_router(backup_router)
|
||||||
|
app.include_router(counts_router)
|
||||||
28
api/routers/counts.py
Normal file
28
api/routers/counts.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from exceptions import bad_key
|
||||||
|
from repos.template import Repo
|
||||||
|
from repos.api_keys import ApiKeyRepo
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Count:
|
||||||
|
prefix: str
|
||||||
|
total_sold: int
|
||||||
|
unique_sold: int
|
||||||
|
|
||||||
|
class CountRepo(Repo):
|
||||||
|
def get_counts(self):
|
||||||
|
self.cur.execute("SELECT * FROM counts")
|
||||||
|
results = self.cur.fetchall()
|
||||||
|
return [Count(*r) for r in results]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
counts_router = APIRouter(prefix="/api/counts")
|
||||||
|
|
||||||
|
@counts_router.get("/")
|
||||||
|
def get_ticket_counts(api_key: str):
|
||||||
|
if not ApiKeyRepo().check_api(api_key):
|
||||||
|
raise bad_key
|
||||||
|
return CountRepo().get_counts()
|
||||||
3
db/Dockerfile
Normal file
3
db/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
FROM mariadb:lts
|
||||||
|
|
||||||
|
COPY schema.sql /docker-entrypoint-initdb.d/tam3-schema.sql
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: mariadb:lts
|
image: dbob16/tam3-db:0.0.1
|
||||||
restart: always
|
restart: always
|
||||||
hostname: mariadb
|
hostname: mariadb
|
||||||
container_name: mariadb
|
container_name: mariadb
|
||||||
@@ -11,7 +11,6 @@ services:
|
|||||||
MARIADB_PASSWORD: tam3
|
MARIADB_PASSWORD: tam3
|
||||||
volumes:
|
volumes:
|
||||||
- "tam3-db:/var/lib/mysql"
|
- "tam3-db:/var/lib/mysql"
|
||||||
- "./schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro"
|
|
||||||
ports:
|
ports:
|
||||||
- 127.0.0.1:3306:3306
|
- 127.0.0.1:3306:3306
|
||||||
adminer:
|
adminer:
|
||||||
|
|||||||
2
webapp/.dockerignore
Normal file
2
webapp/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/node_modules/
|
||||||
|
*.db
|
||||||
23
webapp/Dockerfile
Normal file
23
webapp/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM node:lts-alpine AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN npm install && npm run build
|
||||||
|
|
||||||
|
FROM node:lts-alpine AS prod
|
||||||
|
|
||||||
|
WORKDIR /data
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV DATABASE_URL=file:/data/local.db
|
||||||
|
|
||||||
|
COPY --from=build /app/build/. build/.
|
||||||
|
COPY --from=build /app/package.json /app/drizzle.config.js .
|
||||||
|
COPY --from=build /app/drizzle drizzle
|
||||||
|
COPY deploy/start-server.sh start-server.sh
|
||||||
|
|
||||||
|
RUN npm install --production && npm install drizzle-kit
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["sh", "start-server.sh"]
|
||||||
2
webapp/deploy/start-server.sh
Executable file
2
webapp/deploy/start-server.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
npx drizzle-kit migrate
|
||||||
|
node build
|
||||||
39
webapp/drizzle/0000_init.sql
Normal file
39
webapp/drizzle/0000_init.sql
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
CREATE TABLE `baskets` (
|
||||||
|
`prefix` text,
|
||||||
|
`b_id` integer,
|
||||||
|
`description` text DEFAULT '',
|
||||||
|
`donors` text DEFAULT '',
|
||||||
|
`winning_ticket` integer DEFAULT 0,
|
||||||
|
PRIMARY KEY(`prefix`, `b_id`)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `prefixes` (
|
||||||
|
`name` text PRIMARY KEY NOT NULL,
|
||||||
|
`color` text,
|
||||||
|
`weight` integer DEFAULT 0
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `tickets` (
|
||||||
|
`prefix` text,
|
||||||
|
`t_id` integer,
|
||||||
|
`first_name` text DEFAULT '',
|
||||||
|
`last_name` text DEFAULT '',
|
||||||
|
`phone_number` text DEFAULT '',
|
||||||
|
`preference` text DEFAULT 'CALL',
|
||||||
|
PRIMARY KEY(`prefix`, `t_id`)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE VIEW `combined` AS SELECT b.prefix, b.b_id, b.winning_ticket, CONCAT(t.last_name, ', ', t.first_name) AS winner
|
||||||
|
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 `counts` AS SELECT prefix, COUNT(*) AS total_sold, COUNT(DISTINCT CONCAT(first_name,last_name,phone_number)) AS unique_sold
|
||||||
|
FROM tickets
|
||||||
|
GROUP BY prefix
|
||||||
|
UNION ALL
|
||||||
|
SELECT 'Total' AS prefix, COUNT(*) AS total_sold, COUNT(DISTINCT CONCAT(first_name,last_name,phone_number)) AS unique_sold
|
||||||
|
FROM tickets;;--> statement-breakpoint
|
||||||
|
CREATE VIEW `report` AS SELECT b.prefix, CONCAT(t.last_name, ', ', t.first_name) AS winner_name, t.phone_number, t.preference, b.b_id, b.winning_ticket, b.description
|
||||||
|
FROM baskets b LEFT JOIN tickets t
|
||||||
|
ON b.prefix = t.prefix AND b.winning_ticket = t.t_id
|
||||||
|
ORDER BY b.prefix, winner_name, t.phone_number, t.preference, b.b_id;
|
||||||
290
webapp/drizzle/meta/0000_snapshot.json
Normal file
290
webapp/drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "b981beaa-405c-48f1-8006-35f66d264062",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"baskets": {
|
||||||
|
"name": "baskets",
|
||||||
|
"columns": {
|
||||||
|
"prefix": {
|
||||||
|
"name": "prefix",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"b_id": {
|
||||||
|
"name": "b_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
},
|
||||||
|
"donors": {
|
||||||
|
"name": "donors",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
},
|
||||||
|
"winning_ticket": {
|
||||||
|
"name": "winning_ticket",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"baskets_prefix_b_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"prefix",
|
||||||
|
"b_id"
|
||||||
|
],
|
||||||
|
"name": "baskets_prefix_b_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"prefixes": {
|
||||||
|
"name": "prefixes",
|
||||||
|
"columns": {
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"name": "color",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"weight": {
|
||||||
|
"name": "weight",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"tickets": {
|
||||||
|
"name": "tickets",
|
||||||
|
"columns": {
|
||||||
|
"prefix": {
|
||||||
|
"name": "prefix",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"t_id": {
|
||||||
|
"name": "t_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"name": "first_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"name": "last_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"name": "phone_number",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
},
|
||||||
|
"preference": {
|
||||||
|
"name": "preference",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'CALL'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"tickets_prefix_t_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"prefix",
|
||||||
|
"t_id"
|
||||||
|
],
|
||||||
|
"name": "tickets_prefix_t_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"combined": {
|
||||||
|
"columns": {
|
||||||
|
"prefix": {
|
||||||
|
"name": "prefix",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"b_id": {
|
||||||
|
"name": "b_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"winning_ticket": {
|
||||||
|
"name": "winning_ticket",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"winner": {
|
||||||
|
"name": "winner",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "combined",
|
||||||
|
"isExisting": false,
|
||||||
|
"definition": "SELECT b.prefix, b.b_id, b.winning_ticket, CONCAT(t.last_name, ', ', t.first_name) AS winner\n\tFROM baskets b LEFT JOIN tickets t\n\tON b.prefix = t.prefix AND b.winning_ticket = t.t_id\n\tORDER BY b.prefix, b.b_id"
|
||||||
|
},
|
||||||
|
"counts": {
|
||||||
|
"columns": {
|
||||||
|
"prefix": {
|
||||||
|
"name": "prefix",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"total_sold": {
|
||||||
|
"name": "total_sold",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"unique_sold": {
|
||||||
|
"name": "unique_sold",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "counts",
|
||||||
|
"isExisting": false,
|
||||||
|
"definition": "SELECT prefix, COUNT(*) AS total_sold, COUNT(DISTINCT CONCAT(first_name,last_name,phone_number)) AS unique_sold\n\tFROM tickets\n\tGROUP BY prefix\n\tUNION ALL\n\tSELECT 'Total' AS prefix, COUNT(*) AS total_sold, COUNT(DISTINCT CONCAT(first_name,last_name,phone_number)) AS unique_sold\n\tFROM tickets;"
|
||||||
|
},
|
||||||
|
"report": {
|
||||||
|
"columns": {
|
||||||
|
"prefix": {
|
||||||
|
"name": "prefix",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"winner_name": {
|
||||||
|
"name": "winner_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"name": "phone_number",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"preference": {
|
||||||
|
"name": "preference",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"b_id": {
|
||||||
|
"name": "b_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"winning_ticket": {
|
||||||
|
"name": "winning_ticket",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "report",
|
||||||
|
"isExisting": false,
|
||||||
|
"definition": "SELECT b.prefix, CONCAT(t.last_name, ', ', t.first_name) AS winner_name, t.phone_number, \tt.preference, b.b_id, b.winning_ticket, b.description\n\tFROM baskets b LEFT JOIN tickets t\n\tON b.prefix = t.prefix AND b.winning_ticket = t.t_id\n\tORDER BY b.prefix, winner_name, t.phone_number, t.preference, b.b_id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
webapp/drizzle/meta/_journal.json
Normal file
13
webapp/drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1759085800774,
|
||||||
|
"tag": "0000_init",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hotkeys-js": "^3.13.15",
|
"hotkeys-js": "^3.13.15",
|
||||||
|
"drizzle-kit": "^0.30.2",
|
||||||
"@libsql/client": "^0.14.0"
|
"@libsql/client": "^0.14.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,22 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="300"
|
||||||
|
height="300"
|
||||||
|
viewBox="0 0 79.375 79.375"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-20.218306,-20.438326)">
|
||||||
|
<path
|
||||||
|
id="path10"
|
||||||
|
style="fill:#800000;stroke:#808080;stroke-width:1.06322"
|
||||||
|
d="m 90.181662,41.388444 -60.604352,0.0043 c 0,2.212943 -1.481519,3.631902 -3.512639,4.063504 l -0.02158,10.850643 c 2.046741,0.04733 3.562282,1.625408 3.562282,3.854622 0,2.229214 -1.578039,3.848839 -3.562282,3.937839 l 0.03118,10.520173 c 1.999037,0.142718 3.522231,2.020114 3.522231,4.233054 l 60.695724,0.01054 c 0.09863,-2.128742 1.494926,-3.716455 3.467073,-3.663485 l 0.0103,-11.216367 c -1.875025,-0.235686 -3.579073,-1.512439 -3.579073,-3.741656 0,-2.229216 1.61552,-3.741912 3.564199,-3.845028 L 93.754261,45.362239 C 91.96847,45.081704 90.380789,43.582451 90.181666,41.388427 Z M 31.793484,52.704816 H 46.04333 v 2.935872 h -2.219771 l -3.398965,-0.910595 v 14.730924 h -3.40376 V 54.638242 l -5.217278,1.397909 z m 18.60689,0.166674 h 2.916687 l 4.355128,16.254259 H 55.2337 l -1.671303,-6.236751 h -3.995396 l -1.738693,6.520937 -3.980047,-0.117991 z m 13.207107,1.532448 h 3.894676 l 2.397476,8.946953 2.294594,-8.564201 h 2.601806 l 3.725355,13.903547 H 76.04429 l -2.863444,-10.686845 -1.851408,8.517196 -2.349513,0.0295 -3.416952,-8.67236 -2.85937,10.671736 h -2.598687 z m 21.862852,0.673509 c 2.104651,0 3.573787,1.876237 3.573787,3.809405 0,1.138072 -0.131525,1.947015 -1.160468,2.367944 1.325151,0.467704 1.458361,1.158997 1.458361,2.593276 0,2.135833 -1.701223,3.537066 -3.97737,3.537066 -1.512235,0 -2.587832,-0.545713 -3.16466,-1.637009 -0.265026,-0.498886 -0.421054,-1.091073 -0.483477,-1.9953 l 1.902331,-0.11785 c 0.09354,1.652535 0.23701,1.8861 1.749236,1.8861 1.418686,0 1.775266,-0.238729 1.775266,-1.657418 0,-1.356327 -0.0784,-1.440989 -1.525694,-1.822194 l -1.195478,-0.05892 -0.31878,0.559785 0.206237,-1.97872 c 0.950986,-0.01559 1.803445,0.03993 2.177618,-0.115974 0.608,-0.265026 0.593906,-0.718917 0.593906,-1.514017 0,-1.216015 -0.34974,-1.801463 -1.565763,-1.801463 -0.857456,0 -1.307748,0.256431 -1.619549,0.895628 -0.171551,0.342972 -0.292694,0.67389 -0.308334,1.359849 h -1.813701 c 0.04676,-2.385268 1.467168,-4.310184 3.696532,-4.310184 z m -34.932782,2.460694 -0.637201,2.803253 h 3.208788 l -0.751356,-2.803253 z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -30,6 +30,7 @@ a.styled:hover, button.styled:hover {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import { env } from "$env/dynamic/public";
|
import { env } from "$env/dynamic/public";
|
||||||
import hotkeys from "hotkeys-js";
|
import hotkeys from "hotkeys-js";
|
||||||
|
import favicon from "$lib/assets/favicon.svg"
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
let prefix_name = $state("");
|
let prefix_name = $state("");
|
||||||
@@ -31,7 +32,16 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="main-menu">
|
<div class="main-menu">
|
||||||
<h1>{venue} - Main Menu</h1>
|
<div class="flex-row">
|
||||||
|
<img src="{favicon}" alt="TAM3 Icon - Red ticket with TAM3 on it" class="icon">
|
||||||
|
<h1>{venue} - Main Menu</h1>
|
||||||
|
</div>
|
||||||
|
<div class="universal-reports flex-row tb-margin">
|
||||||
|
<a href="/counts" target="_blank" class="styled">Counts</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Select Prefix:</h2>
|
||||||
|
</div>
|
||||||
<div class="prefix-selector">
|
<div class="prefix-selector">
|
||||||
<select style="width: 100%; box-sizing: border-box;" bind:value={prefix_name}>
|
<select style="width: 100%; box-sizing: border-box;" bind:value={prefix_name}>
|
||||||
{#each all_prefixes as prefix}
|
{#each all_prefixes as prefix}
|
||||||
@@ -63,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.prefix-selector select {
|
img.icon {
|
||||||
width: 100%;
|
max-width: 150px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
17
webapp/src/routes/api/counts/+server.js
Normal file
17
webapp/src/routes/api/counts/+server.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { env } from "$env/dynamic/private";
|
||||||
|
import { db } from "$lib/server/db";
|
||||||
|
import { counts } from "$lib/server/db/schema";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
if (env.TAM3_REMOTE) {
|
||||||
|
const res = await fetch(`${env.TAM3_REMOTE}/api/counts/?api_key=${env.TAM3_REMOTE_KEY}`);
|
||||||
|
if (!res.ok) {
|
||||||
|
return new Response(JSON.stringify({detail: "Unable to fetch counts."}), {status: res.status, statusText: res.statusText})
|
||||||
|
};
|
||||||
|
const data = await res.json();
|
||||||
|
return new Response(JSON.stringify(data), {status: 200, statusText: "Fetched counts successfully."})
|
||||||
|
} else {
|
||||||
|
const data = await db.select().from(counts);
|
||||||
|
return new Response(JSON.stringify(data), {status: 200, statusText: "Loaded counts successfully"})
|
||||||
|
}
|
||||||
|
}
|
||||||
7
webapp/src/routes/counts/+page.js
Normal file
7
webapp/src/routes/counts/+page.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export async function load({ fetch }) {
|
||||||
|
const res = await fetch('/api/counts');
|
||||||
|
const c_data = await res.json();
|
||||||
|
const p_res = await fetch('/api/prefixes');
|
||||||
|
const p_data = await p_res.json();
|
||||||
|
return {counts: c_data, prefixes: p_data}
|
||||||
|
}
|
||||||
61
webapp/src/routes/counts/+page.svelte
Normal file
61
webapp/src/routes/counts/+page.svelte
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<script>
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
const { data } = $props();
|
||||||
|
const counts = data.counts;
|
||||||
|
const prefixes = data.prefixes;
|
||||||
|
|
||||||
|
let colormap = {};
|
||||||
|
for (let prefix of prefixes) {colormap[prefix.name] = prefix.color}
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
document.title = "Counts of tickets entered";
|
||||||
|
setTimeout(() => window.location.reload(true), 60000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Counts of tickets entered</h1>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Prefix</th>
|
||||||
|
<th>Total Tickets</th>
|
||||||
|
<th>Unique Buyers</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each counts as count}
|
||||||
|
<tr class={colormap[count.prefix]}>
|
||||||
|
<td>{count.prefix}</td>
|
||||||
|
<td>{parseInt(count.total_sold).toLocaleString()}</td>
|
||||||
|
<td>{parseInt(count.unique_sold).toLocaleString()}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
thead {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
tbody tr {
|
||||||
|
color: var(--button-fg);
|
||||||
|
background-color: var(--button-bg);
|
||||||
|
td:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody tr td {
|
||||||
|
border: solid 1px black;
|
||||||
|
padding: 0.3rem;
|
||||||
|
}
|
||||||
|
tbody tr:last-child {
|
||||||
|
color: #000000;
|
||||||
|
background-color: #dfdfdf;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user