Compare commits
10 Commits
195d2caf52
...
079155d8a5
| Author | SHA1 | Date | |
|---|---|---|---|
| 079155d8a5 | |||
| d4715f5a03 | |||
| 2738ac6e89 | |||
| 0279ce7078 | |||
| 287e89efa0 | |||
| ec0396b065 | |||
| 70b781cf27 | |||
| 767eb8a339 | |||
| 9067108798 | |||
| 34f0559709 |
@@ -1,3 +1,4 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*/__pycache__/
|
*/__pycache__/
|
||||||
data/
|
data/
|
||||||
|
build.sh
|
||||||
|
|||||||
6
api/build.sh
Executable file
6
api/build.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source ../deployment/common.sh
|
||||||
|
|
||||||
|
docker build -t dbob16/tam3-api:${tam3_version} .
|
||||||
|
docker tag dbob16/tam3-api:${tam3_version} dbob16/tam3-api:latest
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from sys import argv
|
from sys import argv
|
||||||
from exceptions import bad_key
|
|
||||||
|
|
||||||
from repos.api_keys import ApiKeyRepo
|
from repos.api_keys import ApiKeyRepo
|
||||||
|
|
||||||
@@ -13,6 +12,7 @@ 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
|
from routers.counts import counts_router
|
||||||
|
from routers.search import search_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)
|
||||||
@@ -31,4 +31,5 @@ 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)
|
app.include_router(counts_router)
|
||||||
|
app.include_router(search_router)
|
||||||
|
|||||||
16
api/repos/search.py
Normal file
16
api/repos/search.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from .template import Repo
|
||||||
|
from .tickets import Ticket
|
||||||
|
|
||||||
|
|
||||||
|
class SearchRepo(Repo):
|
||||||
|
def SearchTickets(
|
||||||
|
self, first_name: str = "", last_name: str = "", phone_number: str = ""
|
||||||
|
):
|
||||||
|
self.cur.execute(
|
||||||
|
'SELECT * FROM tickets WHERE first_name LIKE %s AND last_name LIKE %s AND phone_number LIKE %s',
|
||||||
|
(f"%{first_name}%", f"%{last_name}%", f"%{phone_number}%"),
|
||||||
|
)
|
||||||
|
records = self.cur.fetchall()
|
||||||
|
if not records:
|
||||||
|
return []
|
||||||
|
return [Ticket(*r) for r in records]
|
||||||
17
api/routers/search.py
Normal file
17
api/routers/search.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from exceptions import bad_key
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from repos.api_keys import ApiKeyRepo
|
||||||
|
from repos.search import SearchRepo
|
||||||
|
|
||||||
|
search_router = APIRouter(prefix="/api/search")
|
||||||
|
|
||||||
|
|
||||||
|
@search_router.get("/tickets/")
|
||||||
|
def search_tickets(
|
||||||
|
api_key: str, first_name: str = "", last_name: str = "", phone_number: str = ""
|
||||||
|
):
|
||||||
|
if not ApiKeyRepo().check_api(api_key):
|
||||||
|
raise bad_key
|
||||||
|
return SearchRepo().SearchTickets(
|
||||||
|
first_name=first_name, last_name=last_name, phone_number=phone_number
|
||||||
|
)
|
||||||
1
db/.dockerignore
Normal file
1
db/.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
build.sh
|
||||||
6
db/build.sh
Executable file
6
db/build.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source ../deployment/common.sh
|
||||||
|
|
||||||
|
docker build -t dbob16/tam3-db:${tam3_version} .
|
||||||
|
docker tag dbob16/tam3-db:${tam3_version} dbob16/tam3-db:latest
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
tam3_version="0.3.0"
|
||||||
|
|
||||||
mkdir -p ~/.config/TAM3/data
|
mkdir -p ~/.config/TAM3/data
|
||||||
|
|
||||||
read -p "Do you want to connect to a remote server? [y or n] " rmserver
|
read -p "Do you want to connect to a remote server? [y or n] " rmserver
|
||||||
@@ -9,9 +11,9 @@ if [ $rmserver = "y" -o $rmserver = "Y" ]; then
|
|||||||
read -p "Enter the protocol, server host/ip, and port like "https://ip_or_host:8443" w/o quotes: " serveraddr
|
read -p "Enter the protocol, server host/ip, and port like "https://ip_or_host:8443" w/o quotes: " serveraddr
|
||||||
read -p "Paste in (Ctrl + Shift + V on most terminal emulators) or enter the api key you generated for your server: " serverapi
|
read -p "Paste in (Ctrl + Shift + V on most terminal emulators) or enter the api key you generated for your server: " serverapi
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if [ -x "$(command -v docker)" ]; then
|
||||||
docker run -d --name=tam3-webclient --restart=always -v ~/.config/TAM3/data:/data:rw,z -e TAM3_REMOTE=$serveraddr -e TAM3_REMOTE_KEY=$serverapi -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:0.2.0
|
docker run -d --name=tam3-webclient --restart=always -v ~/.config/TAM3/data:/data:rw,z -e TAM3_REMOTE=$serveraddr -e TAM3_REMOTE_KEY=$serverapi -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:${tam3_version}
|
||||||
elif [ -x "$(command -v podman)" ]; then
|
elif [ -x "$(command -v podman)" ]; then
|
||||||
podman run -d --name=tam3-webclient --restart=always -v ~/.config/TAM3/data:/data:rw,z -e TAM3_REMOTE=$serveraddr -e TAM3_REMOTE_KEY=$serverapi -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:0.2.0
|
podman run -d --name=tam3-webclient --restart=always -v ~/.config/TAM3/data:/data:rw,z -e TAM3_REMOTE=$serveraddr -e TAM3_REMOTE_KEY=$serverapi -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:${tam3_version}
|
||||||
runin_podman="true"
|
runin_podman="true"
|
||||||
else
|
else
|
||||||
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
||||||
@@ -19,9 +21,9 @@ exit 1
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if [ -x "$(command -v docker)" ]; then
|
||||||
docker run -d --name=tam3-webclient --restart=always -v ~/.config/TAM3/data:/data:rw,z -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:0.2.0
|
docker run -d --name=tam3-webclient --restart=always -v ~/.config/TAM3/data:/data:rw,z -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:${tam3_version}
|
||||||
elif [ -x "$(command -v podman )" ]; then
|
elif [ -x "$(command -v podman )" ]; then
|
||||||
podman run -d --name=tam3-webclient --restart=always -v ~/.config/TAM3/data:/data:rw,z -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:0.2.0
|
podman run -d --name=tam3-webclient --restart=always -v ~/.config/TAM3/data:/data:rw,z -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:${tam3_version}
|
||||||
runin_podman="true"
|
runin_podman="true"
|
||||||
else
|
else
|
||||||
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
||||||
@@ -37,4 +39,4 @@ echo "This script detected that you are running Podman instead of Docker."
|
|||||||
echo "If that is the case you will want to either enable podman 'systemctl enable podman' and podman-restart 'systemctl enable podman-restart' on your system."
|
echo "If that is the case you will want to either enable podman 'systemctl enable podman' and podman-restart 'systemctl enable podman-restart' on your system."
|
||||||
echo "OR add a command to whichever autostart/login startup script process you prefer which runs the command 'podman start tam3-webclient'."
|
echo "OR add a command to whichever autostart/login startup script process you prefer which runs the command 'podman start tam3-webclient'."
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|||||||
5
deployment/client/client-package.sh
Executable file
5
deployment/client/client-package.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source ../common.sh
|
||||||
|
|
||||||
|
tar cvzf tam3-webclient-full_${tam3_version}.tar.gz client-load.sh client-launch.sh tam3-webclient.tar.gz
|
||||||
5
deployment/client/client-save.sh
Executable file
5
deployment/client/client-save.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source ../common.sh
|
||||||
|
|
||||||
|
docker save dbob16/tam3-webclient:${tam3_version} | gzip > tam3-webclient.tar.gz
|
||||||
3
deployment/common.sh
Normal file
3
deployment/common.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
export tam3_version="0.3.0"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
tam3-db:
|
tam3-db:
|
||||||
image: docker.io/dbob16/tam3-db:0.2.0
|
image: docker.io/dbob16/tam3-db:${TAM3_VERSION}
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
MARIADB_RANDOM_ROOT_PASSWORD: 1
|
MARIADB_RANDOM_ROOT_PASSWORD: 1
|
||||||
@@ -16,7 +16,7 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
tam3-api:
|
tam3-api:
|
||||||
image: docker.io/dbob16/tam3-api:0.2.0
|
image: docker.io/dbob16/tam3-api:${TAM3_VERSION}
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
TAM3_DATA_PATH: /data
|
TAM3_DATA_PATH: /data
|
||||||
@@ -25,4 +25,4 @@ services:
|
|||||||
TAM3_DB_USER: tam3
|
TAM3_DB_USER: tam3
|
||||||
TAM3_DB_PASSWD: ${DB_PASSWORD}
|
TAM3_DB_PASSWD: ${DB_PASSWORD}
|
||||||
ports:
|
ports:
|
||||||
- "8000:80"
|
- "8000:80"
|
||||||
|
|||||||
10
deployment/remote_server/delete-key.sh
Executable file
10
deployment/remote_server/delete-key.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
read -p "Enter the key that you want to delete: " apikey
|
||||||
|
|
||||||
|
if [ -x "$(command -v docker)" ]; then
|
||||||
|
docker compose exec tam3-api /app/key.py delete $apikey
|
||||||
|
elif [ -x "$(command -v podman)" ]; then
|
||||||
|
podman compose exec tam3-api /app/key.py delete $apikey
|
||||||
|
else
|
||||||
|
echo "Neither Docker nor Podman are installed. Please install whichever you prefer, then try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
5
deployment/remote_server/package.sh
Executable file
5
deployment/remote_server/package.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source ../common.sh
|
||||||
|
|
||||||
|
tar -cvzf tam3-remote-server_${tam3_version}.tar.gz compose.yml delete-key.sh generate-key.sh list-keys.sh start-server.sh
|
||||||
@@ -4,6 +4,7 @@ gen_password=$(cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)
|
|||||||
|
|
||||||
echo "DB_LOCATION=./tam3-db" > .env
|
echo "DB_LOCATION=./tam3-db" > .env
|
||||||
echo "DB_PASSWORD=${gen_password}" >> .env
|
echo "DB_PASSWORD=${gen_password}" >> .env
|
||||||
|
echo "TAM3_VERSION=0.3.0" >> .env
|
||||||
|
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if [ -x "$(command -v docker)" ]; then
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
@@ -12,4 +13,4 @@ podman compose up -d
|
|||||||
else
|
else
|
||||||
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
16
deployment/remote_server_offline_images/server-save.sh
Executable file
16
deployment/remote_server_offline_images/server-save.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source ../common.sh
|
||||||
|
|
||||||
|
if [ -x "$(command -v docker)" ]; then
|
||||||
|
docker save dbob16/tam3-api:${tam3_version} | gzip > tam3-api.tar.gz
|
||||||
|
docker save dbob16/tam3-db:${tam3_version} | gzip > tam3-db.tar.gz
|
||||||
|
elif [ -x "$(command -v podman)" ]; then
|
||||||
|
podman save dbob16/tam3-api:${tam3_version} | gzip > tam3-api.tar.gz
|
||||||
|
podman save dbob16/tam3-db:${tam3_version} | gzip > tam3-db.tar.gz
|
||||||
|
else
|
||||||
|
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tar -cvzf tam3-server-offline-images_${tam3_version}.tar.gz tam3-api.tar.gz tam3-db.tar.gz server-load.sh
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
tam3-db:
|
tam3-db:
|
||||||
image: docker.io/dbob16/tam3-db:0.2.0
|
image: docker.io/dbob16/tam3-db:${TAM3_VERSION}
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
MARIADB_RANDOM_ROOT_PASSWORD: 1
|
MARIADB_RANDOM_ROOT_PASSWORD: 1
|
||||||
@@ -16,7 +16,7 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
tam3-api:
|
tam3-api:
|
||||||
image: docker.io/dbob16/tam3-api:0.2.0
|
image: docker.io/dbob16/tam3-api:0.2.0${TAM3_VERSION}
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
TAM3_DATA_PATH: /data
|
TAM3_DATA_PATH: /data
|
||||||
@@ -33,4 +33,4 @@ services:
|
|||||||
- "8443:443"
|
- "8443:443"
|
||||||
volumes:
|
volumes:
|
||||||
- "./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro,z"
|
- "./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro,z"
|
||||||
- "./nginx/certs:/certs:ro,z"
|
- "./nginx/certs:/certs:ro,z"
|
||||||
|
|||||||
10
deployment/remote_server_secure/delete-key.sh
Executable file
10
deployment/remote_server_secure/delete-key.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
read -p "Enter the key that you want to delete: " apikey
|
||||||
|
|
||||||
|
if [ -x "$(command -v docker)" ]; then
|
||||||
|
docker compose exec tam3-api /app/key.py delete $apikey
|
||||||
|
elif [ -x "$(command -v podman)" ]; then
|
||||||
|
podman compose exec tam3-api /app/key.py delete $apikey
|
||||||
|
else
|
||||||
|
echo "Neither Docker nor Podman are installed. Please install whichever you prefer, then try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
5
deployment/remote_server_secure/package.sh
Executable file
5
deployment/remote_server_secure/package.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source ../common.sh
|
||||||
|
|
||||||
|
tar -cvzf tam3-remote-server-secure_${tam3_version}.tar.gz compose.yml delete-key.sh generate-key.sh list-keys.sh start-server.sh nginx/
|
||||||
@@ -8,6 +8,7 @@ gen_password=$(cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)
|
|||||||
|
|
||||||
echo "DB_LOCATION=./tam3-db" > .env
|
echo "DB_LOCATION=./tam3-db" > .env
|
||||||
echo "DB_PASSWORD=${gen_password}" >> .env
|
echo "DB_PASSWORD=${gen_password}" >> .env
|
||||||
|
echo "TAM3_VERSION=0.3.0"
|
||||||
|
|
||||||
if [ -x "$(command -v docker)" ]; then
|
if [ -x "$(command -v docker)" ]; then
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
@@ -16,4 +17,4 @@ podman compose up -d
|
|||||||
else
|
else
|
||||||
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
echo "Neither Docker nor Podman are installed. Please install whichever you prefer and try again."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
*.db
|
*.db
|
||||||
|
build.sh
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
FROM docker.io/node:lts-alpine AS build
|
FROM docker.io/node:lts-alpine AS build
|
||||||
|
|
||||||
RUN mkdir /data
|
ENV NODE_ENV=production
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY . .
|
WORKDIR /app
|
||||||
RUN npm install && npm run build
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
RUN mkdir /data
|
||||||
|
|
||||||
|
COPY build/ ./build/
|
||||||
|
COPY drizzle.config.js .
|
||||||
|
COPY drizzle/ ./drizzle/
|
||||||
|
|
||||||
ENV DATABASE_URL=file:/data/local.db
|
ENV DATABASE_URL=file:/data/local.db
|
||||||
ENV SETTINGS_PATH=/data/settings.json
|
ENV SETTINGS_PATH=/data/settings.json
|
||||||
@@ -15,4 +21,4 @@ COPY deploy/start-server.sh start-server.sh
|
|||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["sh", "start-server.sh"]
|
CMD ["sh", "start-server.sh"]
|
||||||
|
|||||||
8
webapp/build.sh
Executable file
8
webapp/build.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
source ../deployment/common.sh
|
||||||
|
|
||||||
|
docker build -t dbob16/tam3-webclient:${tam3_version} .
|
||||||
|
docker tag dbob16/tam3-webclient:${tam3_version} dbob16/tam3-webclient:latest
|
||||||
692
webapp/package-lock.json
generated
692
webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,18 +14,16 @@
|
|||||||
"db:studio": "drizzle-kit studio"
|
"db:studio": "drizzle-kit studio"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@libsql/client": "^0.14.0",
|
|
||||||
"@sveltejs/adapter-node": "^5.2.12",
|
"@sveltejs/adapter-node": "^5.2.12",
|
||||||
"@sveltejs/kit": "^2.22.0",
|
"@sveltejs/kit": "^2.22.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
"drizzle-kit": "^0.30.2",
|
|
||||||
"drizzle-orm": "^0.40.0",
|
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.0.0",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hotkeys-js": "^3.13.15",
|
"hotkeys-js": "^3.13.15",
|
||||||
|
"drizzle-orm": "^0.40.0",
|
||||||
"drizzle-kit": "^0.30.2",
|
"drizzle-kit": "^0.30.2",
|
||||||
"@libsql/client": "^0.14.0"
|
"@libsql/client": "^0.14.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
hotkeys.filter = function(event) {return true}
|
hotkeys.filter = function(event) {return true}
|
||||||
|
hotkeys('alt+q', function(event) {const target = document.getElementById("range_start"); if (target) {target.focus()}})
|
||||||
|
hotkeys('alt+w', function(event) {const target = document.getElementById("range_end"); if (target) {target.focus()}})
|
||||||
hotkeys('alt+n', function(event) {event.preventDefault(); functions.nextPage(); return false});
|
hotkeys('alt+n', function(event) {event.preventDefault(); functions.nextPage(); return false});
|
||||||
hotkeys('alt+b', function(event) {event.preventDefault(); functions.prevPage(); return false});
|
hotkeys('alt+b', function(event) {event.preventDefault(); functions.prevPage(); return false});
|
||||||
hotkeys('alt+j', function(event) {event.preventDefault(); functions.duplicateDown(); return false});
|
hotkeys('alt+j', function(event) {event.preventDefault(); functions.duplicateDown(); return false});
|
||||||
@@ -26,9 +28,9 @@
|
|||||||
<div id="formheader" class="{prefix.color}" bind:offsetHeight={headerHeight}>
|
<div id="formheader" class="{prefix.color}" bind:offsetHeight={headerHeight}>
|
||||||
<div class="flex-row-space tb-margin">
|
<div class="flex-row-space tb-margin">
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<input type="number" bind:value={pagerForm.id_from}>
|
<input type="number" id="range_start" onfocus={(e) => e.target.select()} bind:value={pagerForm.id_from}>
|
||||||
<div style="font-size: 22pt">-</div>
|
<div style="font-size: 22pt">-</div>
|
||||||
<input type="number" bind:value={pagerForm.id_to}>
|
<input type="number" id="range_end" onfocus={(e) => e.target.select()} bind:value={pagerForm.id_to}>
|
||||||
<button class="styled" onclick={() => {
|
<button class="styled" onclick={() => {
|
||||||
if (Math.abs(pagerForm.id_to - pagerForm.id_from) > 800) {
|
if (Math.abs(pagerForm.id_to - pagerForm.id_from) > 800) {
|
||||||
pagerForm.id_to = pagerForm.id_from + 799;
|
pagerForm.id_to = pagerForm.id_from + 799;
|
||||||
@@ -65,4 +67,4 @@
|
|||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
163
webapp/src/lib/components/SearchHeader.svelte
Normal file
163
webapp/src/lib/components/SearchHeader.svelte
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
<script>
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
import hotkeys from "hotkeys-js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
searchForm = $bindable(),
|
||||||
|
headerHeight = $bindable(),
|
||||||
|
functions,
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
hotkeys.filter = function (event) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
hotkeys("alt+n", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.nextPage();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
hotkeys("alt+b", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.prevPage();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
hotkeys("alt+j", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.duplicateDown();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
hotkeys("alt+u", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.duplicateUp();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
hotkeys("alt+l", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.gotoNext();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
hotkeys("alt+o", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.gotoPrev();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
hotkeys("alt+c", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.copy();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
hotkeys("alt+v", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.paste();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
hotkeys("alt+s", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
functions.saveAll();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="formheader" bind:offsetHeight={headerHeight}>
|
||||||
|
<div class="flex-row-space tb-margin">
|
||||||
|
<div class="flex-row">
|
||||||
|
<div class="column">
|
||||||
|
<div>First Name</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
style="width: 20ch"
|
||||||
|
bind:value={searchForm.first_name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div>Last Name</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
style="width: 20ch"
|
||||||
|
bind:value={searchForm.last_name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div>Phone Number</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
style="width: 20ch"
|
||||||
|
bind:value={searchForm.phone_number}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
onclick={() => {
|
||||||
|
if (
|
||||||
|
searchForm.first_name ||
|
||||||
|
searchForm.last_name ||
|
||||||
|
searchForm.phone_number
|
||||||
|
) {
|
||||||
|
functions.refreshPage();
|
||||||
|
}
|
||||||
|
}}>Refresh</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row-space tb-margin">
|
||||||
|
<div class="flex-row">
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
title="Alt + J"
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={functions.duplicateDown}>Duplicate Down</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
title="Alt + U"
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={functions.duplicateUp}>Duplicate Up</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
title="Alt + L"
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={functions.gotoNext}>Next</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
title="Alt + O"
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={functions.gotoPrev}>Previous</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
title="Alt + C"
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={functions.copy}>Copy</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
title="Alt + V"
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={functions.paste}>Paste</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row">
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
title="Alt + S"
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={functions.saveAll}>Save All</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#formheader {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
border-bottom: solid 1px #000000;
|
||||||
|
background-color: #ffffff;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,93 +1,142 @@
|
|||||||
<script>
|
<script>
|
||||||
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 { setContext } from "svelte";
|
||||||
import favicon from "$lib/assets/favicon.svg"
|
import hotkeys from "hotkeys-js";
|
||||||
|
import favicon from "$lib/assets/favicon.svg";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
const all_prefixes = [...data.prefixes];
|
const all_prefixes = $derived(data.prefixes);
|
||||||
let prefix_name = $state("");
|
let prefix_name = $state("");
|
||||||
let current_prefix = $state({name: "", color: "", weight: 0});
|
let current_prefix = $state({ name: "", color: "", weight: 0 });
|
||||||
let admin_mode = $state(false);
|
let admin_mode = $state(false);
|
||||||
const venue = env.PUBLIC_TAM3_VENUE || "TAM3";
|
const venue = env.PUBLIC_TAM3_VENUE || "TAM3";
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const new_prefix = all_prefixes.find((prefix) => prefix.name === prefix_name);
|
const new_prefix = all_prefixes.find(
|
||||||
if (new_prefix) {
|
(prefix) => prefix.name === prefix_name,
|
||||||
current_prefix = {...new_prefix};
|
);
|
||||||
|
if (new_prefix) {
|
||||||
|
current_prefix = { ...new_prefix };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
hotkeys.filter = function (event) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
hotkeys("alt+a", function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
admin_mode = !admin_mode;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
if (all_prefixes[0]) {
|
||||||
|
prefix_name = all_prefixes[0].name;
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
document.title = `${venue} - Main Menu`;
|
|
||||||
hotkeys.filter = function(event) {return true};
|
|
||||||
hotkeys('alt+a', function(event) {event.preventDefault(); admin_mode = !admin_mode; return false;});
|
|
||||||
setTimeout(() => {
|
|
||||||
if (all_prefixes[0]) {
|
|
||||||
prefix_name = all_prefixes[0].name;
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{venue} - Main Menu</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<div class="main-menu">
|
<div class="main-menu">
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<img src="{favicon}" alt="TAM3 Icon - Red ticket with TAM3 on it" class="icon">
|
<img
|
||||||
<h1>{venue} - Main Menu</h1>
|
src={favicon}
|
||||||
</div>
|
alt="TAM3 Icon - Red ticket with TAM3 on it"
|
||||||
{#if all_prefixes.length > 0}
|
class="icon"
|
||||||
<div class="universal-reports flex-row tb-margin">
|
/>
|
||||||
<a href="/counts" target="_blank" class="styled">Counts</a>
|
<h1>{venue} - Main Menu</h1>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h2>Current Prefix: {current_prefix.name}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="change_title">
|
|
||||||
<h2>Change Prefix:</h2>
|
|
||||||
</div>
|
|
||||||
<div class="prefix-selector flex-row">
|
|
||||||
{#each all_prefixes as prefix}
|
|
||||||
<div class="{prefix.color} p025{prefix.name === prefix_name ? " active" : ""}">
|
|
||||||
<button class="styled" onclick={() => {
|
|
||||||
prefix_name = prefix.name;
|
|
||||||
}}>{prefix.name}</button>
|
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{#if all_prefixes.length > 0}
|
||||||
</div>
|
<div class="universal-reports flex-row tb-margin">
|
||||||
<div><h2>Forms:</h2></div>
|
<a href="/counts" target="_blank" class="styled">Counts</a>
|
||||||
<div class="flex-row {current_prefix.color}">
|
<a href="/sheets" target="_blank" class="styled">Print Sheets</a>
|
||||||
<a href="/tickets/{current_prefix.name}/" target="_blank" class="styled">Tickets</a>
|
</div>
|
||||||
<a href="/baskets/{current_prefix.name}/" target="_blank" class="styled">Baskets</a>
|
<div>
|
||||||
<a href="/drawing/{current_prefix.name}/" target="_blank" class="styled">Drawing</a>
|
<h2>Current Prefix: {current_prefix.name}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div><h2>Reports:</h2></div>
|
<div class="change_title">
|
||||||
<div class="flex-row {current_prefix.color}">
|
<h2>Change Prefix:</h2>
|
||||||
<a href="/reports/byname/{current_prefix.name}/" target="_blank" class="styled">By Name</a>
|
</div>
|
||||||
<a href="/reports/bybasket/{current_prefix.name}/" target="_blank" class="styled">By Basket ID</a>
|
<div class="prefix-selector flex-row">
|
||||||
</div>
|
{#each all_prefixes as prefix}
|
||||||
{:else}
|
<div
|
||||||
<p>There aren't any prefixes available, please create them.</p>
|
class="{prefix.color} p025{prefix.name === prefix_name
|
||||||
{/if}
|
? ' active'
|
||||||
|
: ''}"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
onclick={() => {
|
||||||
|
prefix_name = prefix.name;
|
||||||
|
}}>{prefix.name}</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div><h2>Forms:</h2></div>
|
||||||
|
<div class="flex-row {current_prefix.color}">
|
||||||
|
<a
|
||||||
|
href="/tickets/{current_prefix.name}/"
|
||||||
|
target="_blank"
|
||||||
|
class="styled">Tickets</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/baskets/{current_prefix.name}/"
|
||||||
|
target="_blank"
|
||||||
|
class="styled">Baskets</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/drawing/{current_prefix.name}/"
|
||||||
|
target="_blank"
|
||||||
|
class="styled">Drawing</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div><h2>Reports:</h2></div>
|
||||||
|
<div class="flex-row {current_prefix.color}">
|
||||||
|
<a
|
||||||
|
href="/reports/byname/{current_prefix.name}/"
|
||||||
|
target="_blank"
|
||||||
|
class="styled">By Name</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/reports/bybasket/{current_prefix.name}/"
|
||||||
|
target="_blank"
|
||||||
|
class="styled">By Basket ID</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p>There aren't any prefixes available, please create them.</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if admin_mode}
|
{#if admin_mode}
|
||||||
<div><h2>Admin Mode:</h2></div>
|
<div><h2>Admin Mode:</h2></div>
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<a href="/prefixes" target="_blank" class="styled">Prefix Editor</a>
|
<a href="/prefixes" target="_blank" class="styled">Prefix Editor</a>
|
||||||
<a href="/backuprestore" target="_blank" class="styled">Backup/Restore</a>
|
<a href="/search/tickets" target="_blank" class="styled"
|
||||||
<a href="/settings" target="_blank" class="styled">Settings</a>
|
>Search Tickets</a
|
||||||
</div>
|
>
|
||||||
|
<a href="/backuprestore" target="_blank" class="styled"
|
||||||
|
>Backup/Restore</a
|
||||||
|
>
|
||||||
|
<a href="/settings" target="_blank" class="styled">Settings</a>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="status tb-margin">
|
<div class="status tb-margin">
|
||||||
{data.status}
|
{data.status}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="annotation">
|
<div class="annotation">
|
||||||
<p>Ticket Auction Manager 3 by Dilan Gilluly</p>
|
<p>Ticket Auction Manager 3 by Dilan Gilluly</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
img.icon {
|
img.icon {
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
49
webapp/src/routes/api/search/tickets/+server.js
Normal file
49
webapp/src/routes/api/search/tickets/+server.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { readSettings } from "$lib/server/settings";
|
||||||
|
import { db } from "$lib/server/db/index.js";
|
||||||
|
import { tickets } from "$lib/server/db/schema.js";
|
||||||
|
import { and, like, sql } from "drizzle-orm";
|
||||||
|
|
||||||
|
export async function GET({ url }) {
|
||||||
|
const env = readSettings();
|
||||||
|
const sFirstName = url.searchParams.get("first_name") || "",
|
||||||
|
sLastName = url.searchParams.get("last_name") || "",
|
||||||
|
sPhoneNumber = url.searchParams.get("phone_number") || "";
|
||||||
|
|
||||||
|
if (env.TAM3_REMOTE) {
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
api_key: env.TAM3_REMOTE_KEY,
|
||||||
|
first_name: sFirstName,
|
||||||
|
last_name: sLastName,
|
||||||
|
phone_number: sPhoneNumber,
|
||||||
|
});
|
||||||
|
const res = await fetch(
|
||||||
|
`${env.TAM3_REMOTE}/api/search/tickets/?${searchParams.toString()}`,
|
||||||
|
);
|
||||||
|
if (!res.ok) {
|
||||||
|
return new Response(JSON.stringify([]), {
|
||||||
|
status: res.status,
|
||||||
|
statusText: res.statusText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
return new Response(JSON.stringify(data), {
|
||||||
|
status: 200,
|
||||||
|
statusText: "Fetched successfully",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const results = await db
|
||||||
|
.select()
|
||||||
|
.from(tickets)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
like(tickets.first_name, `%${sFirstName}%`),
|
||||||
|
like(tickets.last_name, `%${sLastName}%`),
|
||||||
|
like(tickets.phone_number, `%${sPhoneNumber}%`),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return new Response(JSON.stringify(results), {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +1,41 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/environment';
|
import { browser } from "$app/environment";
|
||||||
import FormHeader from '$lib/components/FormHeader.svelte';
|
import FormHeader from "$lib/components/FormHeader.svelte";
|
||||||
import hotkeys from 'hotkeys-js';
|
import hotkeys from "hotkeys-js";
|
||||||
|
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
const prefix = {...data.prefix};
|
const prefix = $derived(data.prefix);
|
||||||
let pagerForm = $state({id_from: 0, id_to: 0});
|
let pagerForm = $state({ id_from: 0, id_to: 0 });
|
||||||
let current_idx = $state(0);
|
let current_idx = $state(0);
|
||||||
let next_idx = $derived(current_idx+1);
|
let next_idx = $derived(current_idx + 1);
|
||||||
let prev_idx = $derived(current_idx-1)
|
let prev_idx = $derived(current_idx - 1);
|
||||||
let current_baskets = $state([]);
|
let current_baskets = $state([]);
|
||||||
let copy_buffer = $state({prefix: prefix.name, b_id: 0, description: "", donors: "", winning_ticket: 0});
|
let copy_buffer = $state({});
|
||||||
let headerHeight = $state()
|
let headerHeight = $state();
|
||||||
|
|
||||||
function changeFocus(idx) {
|
function changeFocus(idx) {
|
||||||
const focusDe = document.getElementById(`${idx}_de`);
|
const focusDe = document.getElementById(`${idx}_de`);
|
||||||
if (focusDe) {
|
if (focusDe) {
|
||||||
focusDe.select();
|
focusDe.select();
|
||||||
focusDe.scrollIntoView({block: "center"});
|
focusDe.scrollIntoView({ block: "center" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const functions = {
|
const functions = {
|
||||||
refreshPage: async () => {
|
refreshPage: async () => {
|
||||||
if (current_baskets.filter(basket => basket.changed === true).length > 0) {
|
if (
|
||||||
functions.saveAll()
|
current_baskets.filter((basket) => basket.changed === true)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
|
functions.saveAll();
|
||||||
}
|
}
|
||||||
const res = await fetch(`/api/baskets/${prefix.name}/${pagerForm.id_from}/${pagerForm.id_to}`);
|
const res = await fetch(
|
||||||
|
`/api/baskets/${prefix.name}/${pagerForm.id_from}/${pagerForm.id_to}`,
|
||||||
|
);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
current_baskets = [...data];
|
current_baskets = [...data];
|
||||||
setTimeout(() => changeFocus(0), 100)
|
setTimeout(() => changeFocus(0), 100);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
prevPage: () => {
|
prevPage: () => {
|
||||||
@@ -47,7 +52,12 @@
|
|||||||
},
|
},
|
||||||
duplicateDown: () => {
|
duplicateDown: () => {
|
||||||
if (current_baskets[next_idx]) {
|
if (current_baskets[next_idx]) {
|
||||||
current_baskets[next_idx] = {...current_baskets[current_idx], b_id: current_baskets[next_idx].b_id, winning_ticket: current_baskets[next_idx].winning_ticket, changed: true};
|
current_baskets[next_idx] = {
|
||||||
|
...current_baskets[current_idx],
|
||||||
|
b_id: current_baskets[next_idx].b_id,
|
||||||
|
winning_ticket: current_baskets[next_idx].winning_ticket,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
changeFocus(next_idx);
|
changeFocus(next_idx);
|
||||||
} else {
|
} else {
|
||||||
changeFocus(next_idx);
|
changeFocus(next_idx);
|
||||||
@@ -55,7 +65,12 @@
|
|||||||
},
|
},
|
||||||
duplicateUp: () => {
|
duplicateUp: () => {
|
||||||
if (prev_idx >= 0) {
|
if (prev_idx >= 0) {
|
||||||
current_baskets[prev_idx] = {...current_baskets[current_idx], b_id: current_baskets[prev_idx].b_id, winning_ticket: current_baskets[prev_idx].winning_ticket, changed: true};
|
current_baskets[prev_idx] = {
|
||||||
|
...current_baskets[current_idx],
|
||||||
|
b_id: current_baskets[prev_idx].b_id,
|
||||||
|
winning_ticket: current_baskets[prev_idx].winning_ticket,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
changeFocus(prev_idx);
|
changeFocus(prev_idx);
|
||||||
} else {
|
} else {
|
||||||
changeFocus(prev_idx);
|
changeFocus(prev_idx);
|
||||||
@@ -76,35 +91,56 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
copy: () => {
|
copy: () => {
|
||||||
copy_buffer = {...current_baskets[current_idx]};
|
copy_buffer = { ...current_baskets[current_idx] };
|
||||||
},
|
},
|
||||||
paste: () => {
|
paste: () => {
|
||||||
current_baskets[current_idx] = {...copy_buffer, b_id: current_baskets[current_idx].b_id, changed: true}
|
if (Object.keys(copy_buffer).length !== 0) {
|
||||||
|
current_baskets[current_idx] = {
|
||||||
|
...copy_buffer,
|
||||||
|
b_id: current_baskets[current_idx].b_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
changeFocus(current_idx);
|
||||||
},
|
},
|
||||||
saveAll: async () => {
|
saveAll: async () => {
|
||||||
const to_save = current_baskets.filter((basket) => basket.changed === true);
|
const to_save = current_baskets.filter(
|
||||||
const res = await fetch(`/api/baskets`, {body: JSON.stringify(to_save), method: 'POST', headers: {'Content-Type': 'application/json'}});
|
(basket) => basket.changed === true,
|
||||||
|
);
|
||||||
|
const res = await fetch(`/api/baskets`, {
|
||||||
|
body: JSON.stringify(to_save),
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
for (let basket of current_baskets) {basket.changed = false};
|
for (let basket of current_baskets) {
|
||||||
|
basket.changed = false;
|
||||||
|
}
|
||||||
changeFocus(0);
|
changeFocus(0);
|
||||||
};
|
}
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
document.title = `${prefix.name} Basket Entry`
|
window.addEventListener("beforeunload", function (e) {
|
||||||
window.addEventListener("beforeunload", function(e) {
|
if (
|
||||||
if (current_baskets.filter(basket => basket.changed === true).length > 0) {
|
current_baskets.filter((basket) => basket.changed === true)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{prefix.name} Basket Entry</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<h1>{prefix.name} Basket Entry</h1>
|
<h1>{prefix.name} Basket Entry</h1>
|
||||||
<FormHeader {prefix} {functions} bind:pagerForm bind:headerHeight />
|
<FormHeader {prefix} {functions} bind:pagerForm bind:headerHeight />
|
||||||
<table>
|
<table>
|
||||||
<thead style="top: {headerHeight+2}px">
|
<thead style="top: {headerHeight + 2}px">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 12ch">Basket ID</th>
|
<th style="width: 12ch">Basket ID</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
@@ -114,12 +150,32 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each current_baskets as basket, idx}
|
{#each current_baskets as basket, idx}
|
||||||
<tr onfocusin={() => current_idx = idx}>
|
<tr onfocusin={() => (current_idx = idx)}>
|
||||||
<td>{basket.b_id}</td>
|
<td>{basket.b_id}</td>
|
||||||
<td><input type="text" id="{idx}_de" onchange={() => basket.changed = true} bind:value={basket.description}></td>
|
<td
|
||||||
<td><input type="text" id="{idx}_do" onchange={() => basket.changed = true} bind:value={basket.donors}></td>
|
><input
|
||||||
<td><button tabindex="-1" onclick={() => basket.changed = !basket.changed}>{basket.changed ? "Y" : "N"}</button></td>
|
type="text"
|
||||||
</tr>
|
id="{idx}_de"
|
||||||
|
onchange={() => (basket.changed = true)}
|
||||||
|
bind:value={basket.description}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><input
|
||||||
|
type="text"
|
||||||
|
id="{idx}_do"
|
||||||
|
onchange={() => (basket.changed = true)}
|
||||||
|
bind:value={basket.donors}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><button
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={() => (basket.changed = !basket.changed)}
|
||||||
|
>{basket.changed ? "Y" : "N"}</button
|
||||||
|
></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -148,10 +204,11 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
border: solid 1px #000000;
|
border: solid 1px #000000;
|
||||||
}
|
}
|
||||||
input, button {
|
input,
|
||||||
|
button {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/environment';
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
const counts = data.counts;
|
const counts = $derived(data.counts);
|
||||||
const prefixes = data.prefixes;
|
function copyPrefixes() {
|
||||||
|
return [...data.prefixes];
|
||||||
|
}
|
||||||
|
const prefixes = $state(copyPrefixes());
|
||||||
|
|
||||||
let colormap = {};
|
let colormap = {};
|
||||||
for (let prefix of prefixes) {colormap[prefix.name] = prefix.color}
|
for (let prefix of prefixes) {
|
||||||
|
colormap[prefix.name] = prefix.color;
|
||||||
|
}
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
document.title = "Counts of tickets entered";
|
|
||||||
setTimeout(() => window.location.reload(true), 60000);
|
setTimeout(() => window.location.reload(true), 60000);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Counts of tickets entered</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<h1>Counts of tickets entered</h1>
|
<h1>Counts of tickets entered</h1>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -25,11 +33,11 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each counts as count}
|
{#each counts as count}
|
||||||
<tr class={colormap[count.prefix]}>
|
<tr class={colormap[count.prefix]}>
|
||||||
<td>{count.prefix}</td>
|
<td>{count.prefix}</td>
|
||||||
<td>{parseInt(count.total_sold).toLocaleString()}</td>
|
<td>{parseInt(count.total_sold).toLocaleString()}</td>
|
||||||
<td>{parseInt(count.unique_sold).toLocaleString()}</td>
|
<td>{parseInt(count.unique_sold).toLocaleString()}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -58,4 +66,4 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,32 +1,37 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/environment';
|
import { browser } from "$app/environment";
|
||||||
import FormHeader from '$lib/components/FormHeader.svelte';
|
import FormHeader from "$lib/components/FormHeader.svelte";
|
||||||
import { focusElement } from '$lib/focusElement.js';
|
import { focusElement } from "$lib/focusElement.js";
|
||||||
|
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
const prefix = {...data.prefix};
|
const prefix = $derived(data.prefix);
|
||||||
let pagerForm = $state({id_from: 0, id_to: 0});
|
let pagerForm = $state({ id_from: 0, id_to: 0 });
|
||||||
let current_idx = $state(0);
|
let current_idx = $state(0);
|
||||||
let next_idx = $derived(current_idx+1);
|
let next_idx = $derived(current_idx + 1);
|
||||||
let prev_idx = $derived(current_idx-1);
|
let prev_idx = $derived(current_idx - 1);
|
||||||
let current_drawings = $state([]);
|
let current_drawings = $state([]);
|
||||||
let copy_buffer = $state({prefix: prefix.name, b_id: 1, winning_ticket: 0, winner: ", "});
|
let copy_buffer = $state({});
|
||||||
let headerHeight = $state()
|
let headerHeight = $state();
|
||||||
|
|
||||||
function changeFocus(idx) {
|
function changeFocus(idx) {
|
||||||
const focusWt = document.getElementById(`${idx}_wt`);
|
const focusWt = document.getElementById(`${idx}_wt`);
|
||||||
if (focusWt) {
|
if (focusWt) {
|
||||||
focusWt.select();
|
focusWt.select();
|
||||||
focusWt.scrollIntoView({block: "center"});
|
focusWt.scrollIntoView({ block: "center" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const functions = {
|
const functions = {
|
||||||
refreshPage: async () => {
|
refreshPage: async () => {
|
||||||
if (current_drawings.filter(drawing => drawing.changed === true).length > 0) {
|
if (
|
||||||
functions.saveAll()
|
current_drawings.filter((drawing) => drawing.changed === true)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
|
functions.saveAll();
|
||||||
}
|
}
|
||||||
const res = await fetch(`/api/combined/${prefix.name}/${pagerForm.id_from}/${pagerForm.id_to}`);
|
const res = await fetch(
|
||||||
|
`/api/combined/${prefix.name}/${pagerForm.id_from}/${pagerForm.id_to}`,
|
||||||
|
);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
current_drawings = [...data];
|
current_drawings = [...data];
|
||||||
@@ -47,7 +52,11 @@
|
|||||||
},
|
},
|
||||||
duplicateDown: () => {
|
duplicateDown: () => {
|
||||||
if (current_drawings[next_idx]) {
|
if (current_drawings[next_idx]) {
|
||||||
current_drawings[next_idx] = {...current_drawings[current_idx], b_id: current_drawings[next_idx].b_id, changed: true};
|
current_drawings[next_idx] = {
|
||||||
|
...current_drawings[current_idx],
|
||||||
|
b_id: current_drawings[next_idx].b_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
changeFocus(next_idx);
|
changeFocus(next_idx);
|
||||||
} else {
|
} else {
|
||||||
changeFocus(current_idx);
|
changeFocus(current_idx);
|
||||||
@@ -55,7 +64,11 @@
|
|||||||
},
|
},
|
||||||
duplicateUp: () => {
|
duplicateUp: () => {
|
||||||
if (prev_idx >= 0) {
|
if (prev_idx >= 0) {
|
||||||
current_drawings[prev_idx] = {...current_drawing[current_idx], b_id: current_drawings[prev_idx].b_id, changed: true};
|
current_drawings[prev_idx] = {
|
||||||
|
...current_drawing[current_idx],
|
||||||
|
b_id: current_drawings[prev_idx].b_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
changeFocus(prev_idx);
|
changeFocus(prev_idx);
|
||||||
} else {
|
} else {
|
||||||
changeFocus(current_idx);
|
changeFocus(current_idx);
|
||||||
@@ -76,35 +89,56 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
copy: () => {
|
copy: () => {
|
||||||
copy_buffer = {...current_drawings[current_idx]};
|
copy_buffer = { ...current_drawings[current_idx] };
|
||||||
},
|
},
|
||||||
paste: () => {
|
paste: () => {
|
||||||
current_drawings[current_idx] = {...copy_buffer, b_id: current_drawings[current_idx], changed: true};
|
if (Object.keys(copy_buffer).length !== 0) {
|
||||||
|
current_drawings[current_idx] = {
|
||||||
|
...copy_buffer,
|
||||||
|
b_id: current_drawings[current_idx],
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
changeFocus(current_idx);
|
||||||
},
|
},
|
||||||
saveAll: async () => {
|
saveAll: async () => {
|
||||||
const to_save = current_drawings.filter((drawing) => drawing.changed === true);
|
const to_save = current_drawings.filter(
|
||||||
const res = await fetch("/api/combined", {body: JSON.stringify(to_save), method: 'POST', headers: {'Content-Type': 'application/json'}});
|
(drawing) => drawing.changed === true,
|
||||||
|
);
|
||||||
|
const res = await fetch("/api/combined", {
|
||||||
|
body: JSON.stringify(to_save),
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
for (let drawing of current_drawings) {drawing.changed = false};
|
for (let drawing of current_drawings) {
|
||||||
|
drawing.changed = false;
|
||||||
|
}
|
||||||
changeFocus(0);
|
changeFocus(0);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
document.title = `${prefix.name} Drawing Form`
|
window.addEventListener("beforeunload", function (e) {
|
||||||
window.addEventListener("beforeunload", function(e) {
|
if (
|
||||||
if (current_drawings.filter(drawing => drawing.changed === true).length > 0) {
|
current_drawings.filter((drawing) => drawing.changed === true)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{prefix.name} Drawing Form</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<h1>{prefix.name} Drawing Form</h1>
|
<h1>{prefix.name} Drawing Form</h1>
|
||||||
<FormHeader {prefix} {functions} bind:pagerForm bind:headerHeight />
|
<FormHeader {prefix} {functions} bind:pagerForm bind:headerHeight />
|
||||||
<table>
|
<table>
|
||||||
<thead style="top: {headerHeight+2}px">
|
<thead style="top: {headerHeight + 2}px">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 12ch">Basket ID</th>
|
<th style="width: 12ch">Basket ID</th>
|
||||||
<th style="width: 20ch">Winning Number</th>
|
<th style="width: 20ch">Winning Number</th>
|
||||||
@@ -114,19 +148,32 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each current_drawings as drawing, idx}
|
{#each current_drawings as drawing, idx}
|
||||||
<tr onfocusin={() => current_idx = idx}>
|
<tr onfocusin={() => (current_idx = idx)}>
|
||||||
<td>{drawing.b_id}</td>
|
<td>{drawing.b_id}</td>
|
||||||
<td><input type="number" id="{idx}_wt" bind:value={drawing.winning_ticket} onfocus={focusElement} onchange={async () => {
|
<td
|
||||||
drawing.changed = true;
|
><input
|
||||||
const res = await fetch(`/api/tickets/${prefix.name}/${drawing.winning_ticket}`);
|
type="number"
|
||||||
if (res.ok) {
|
id="{idx}_wt"
|
||||||
const t_data = await res.json()
|
bind:value={drawing.winning_ticket}
|
||||||
drawing.winner = `${t_data.last_name}, ${t_data.first_name}`
|
onfocus={focusElement}
|
||||||
}
|
onchange={async () => {
|
||||||
}}></td>
|
drawing.changed = true;
|
||||||
<td>{drawing.winner}</td>
|
const res = await fetch(
|
||||||
<td><button tabindex="-1">{drawing.changed ? "Y" : "N"}</button></td>
|
`/api/tickets/${prefix.name}/${drawing.winning_ticket}`,
|
||||||
</tr>
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const t_data = await res.json();
|
||||||
|
drawing.winner = `${t_data.last_name}, ${t_data.first_name}`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td>{drawing.winner}</td>
|
||||||
|
<td
|
||||||
|
><button tabindex="-1">{drawing.changed ? "Y" : "N"}</button
|
||||||
|
></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -155,10 +202,11 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
border: solid 1px #000000;
|
border: solid 1px #000000;
|
||||||
}
|
}
|
||||||
input, button {
|
input,
|
||||||
|
button {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,33 +1,53 @@
|
|||||||
<script>
|
<script>
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from "$env/dynamic/public";
|
||||||
import { browser } from '$app/environment';
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
const prefix = {...data.prefix};
|
const prefix = $derived(data.prefix);
|
||||||
const report_data = data.report;
|
const report_data = $derived(data.report);
|
||||||
let show_data = $state([...report_data]);
|
function copyReportData() {
|
||||||
let report_subject = $state("All Preferences");
|
return [...report_data];
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
document.title = `${prefix.name} Report By Basket ID`
|
|
||||||
}
|
}
|
||||||
|
let show_data = $state(copyReportData());
|
||||||
|
let report_subject = $state("All Preferences");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{prefix.name} Report By Basket ID</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<div id="reportheader">
|
<div id="reportheader">
|
||||||
<div class="flex-row-space {prefix.color}">
|
<div class="flex-row-space {prefix.color}">
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<button class="styled" onclick={() => {
|
<button
|
||||||
show_data = [...report_data];
|
class="styled"
|
||||||
report_subject = "All Preferences";
|
onclick={() => {
|
||||||
}}>All Preferences</button>
|
show_data = copyReportData();
|
||||||
<button class="styled" onclick={() => {
|
report_subject = "All Preferences";
|
||||||
show_data = [...report_data.filter((entry) => entry.preference === "CALL")];
|
}}>All Preferences</button
|
||||||
report_subject = "CALL Preference"
|
>
|
||||||
}}>Call</button>
|
<button
|
||||||
<button class="styled" onclick={() => {
|
class="styled"
|
||||||
show_data = [...report_data.filter((entry) => entry.preference === "TEXT")];
|
onclick={() => {
|
||||||
report_subject = "TEXT Preference";
|
show_data = [
|
||||||
}}>Text</button>
|
...report_data.filter(
|
||||||
|
(entry) => entry.preference === "CALL",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
report_subject = "CALL Preference";
|
||||||
|
}}>Call</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
onclick={() => {
|
||||||
|
show_data = [
|
||||||
|
...report_data.filter(
|
||||||
|
(entry) => entry.preference === "TEXT",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
report_subject = "TEXT Preference";
|
||||||
|
}}>Text</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<button class="styled" onclick={() => window.print()}>Print</button>
|
<button class="styled" onclick={() => window.print()}>Print</button>
|
||||||
@@ -35,35 +55,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="90"><h1>{prefix.name} - Report - {report_subject}</h1></th>
|
<th colspan="90"
|
||||||
</tr>
|
><h1>{prefix.name} - Report - {report_subject}</h1></th
|
||||||
<tr>
|
>
|
||||||
<th>Basket ID</th>
|
</tr>
|
||||||
<th>Description</th>
|
<tr>
|
||||||
<th>Ticket #</th>
|
<th>Basket ID</th>
|
||||||
<th>Winner Name</th>
|
<th>Description</th>
|
||||||
<th>Phone Number</th>
|
<th>Ticket #</th>
|
||||||
</tr>
|
<th>Winner Name</th>
|
||||||
</thead>
|
<th>Phone Number</th>
|
||||||
<tbody>
|
</tr>
|
||||||
{#each show_data as report_entry}
|
</thead>
|
||||||
<tr>
|
<tbody>
|
||||||
<td>{report_entry.b_id}</td>
|
{#each show_data as report_entry}
|
||||||
<td>{report_entry.description}</td>
|
<tr>
|
||||||
<td>{report_entry.winning_ticket}</td>
|
<td>{report_entry.b_id}</td>
|
||||||
<td>{report_entry.winner_name}</td>
|
<td>{report_entry.description}</td>
|
||||||
<td>{report_entry.phone_number}</td>
|
<td>{report_entry.winning_ticket}</td>
|
||||||
</tr>
|
<td>{report_entry.winner_name}</td>
|
||||||
{/each}
|
<td>{report_entry.phone_number}</td>
|
||||||
</tbody>
|
</tr>
|
||||||
<tfoot>
|
{/each}
|
||||||
<tr>
|
</tbody>
|
||||||
<td colspan="3">{env.PUBLIC_TAM3_VENUE || ""}</td>
|
<tfoot>
|
||||||
<td colspan="2" style="text-align: right">TAM3 by Dilan Gilluly</td>
|
<tr>
|
||||||
</tr>
|
<td colspan="3">{env.PUBLIC_TAM3_VENUE || ""}</td>
|
||||||
</tfoot>
|
<td colspan="2" style="text-align: right">TAM3 by Dilan Gilluly</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -76,7 +98,6 @@
|
|||||||
border: solid 1px black;
|
border: solid 1px black;
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
table tbody tr:nth-child(2n) {
|
table tbody tr:nth-child(2n) {
|
||||||
background-color: #dddddd;
|
background-color: #dddddd;
|
||||||
@@ -92,4 +113,4 @@
|
|||||||
margin: 0.25in;
|
margin: 0.25in;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,33 +1,53 @@
|
|||||||
<script>
|
<script>
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from "$env/dynamic/public";
|
||||||
import { browser } from '$app/environment';
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
const prefix = {...data.prefix};
|
const prefix = $derived(data.prefix);
|
||||||
const report_data = data.report;
|
const report_data = $derived(data.report);
|
||||||
let show_data = $state([...report_data]);
|
function copyReportData() {
|
||||||
let report_subject = $state("All Preferences");
|
return [...report_data];
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
document.title = `${prefix.name} Report By Name`
|
|
||||||
}
|
}
|
||||||
|
let show_data = $state(copyReportData());
|
||||||
|
let report_subject = $state("All Preferences");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{prefix.name} Report By Name</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<div id="reportheader">
|
<div id="reportheader">
|
||||||
<div class="flex-row-space {prefix.color}">
|
<div class="flex-row-space {prefix.color}">
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<button class="styled" onclick={() => {
|
<button
|
||||||
show_data = [...report_data];
|
class="styled"
|
||||||
report_subject = "All Preferences";
|
onclick={() => {
|
||||||
}}>All Preferences</button>
|
show_data = copyReportData();
|
||||||
<button class="styled" onclick={() => {
|
report_subject = "All Preferences";
|
||||||
show_data = [...report_data.filter((entry) => entry.preference === "CALL")];
|
}}>All Preferences</button
|
||||||
report_subject = "CALL Preference"
|
>
|
||||||
}}>Call</button>
|
<button
|
||||||
<button class="styled" onclick={() => {
|
class="styled"
|
||||||
show_data = [...report_data.filter((entry) => entry.preference === "TEXT")];
|
onclick={() => {
|
||||||
report_subject = "TEXT Preference";
|
show_data = [
|
||||||
}}>Text</button>
|
...report_data.filter(
|
||||||
|
(entry) => entry.preference === "CALL",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
report_subject = "CALL Preference";
|
||||||
|
}}>Call</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="styled"
|
||||||
|
onclick={() => {
|
||||||
|
show_data = [
|
||||||
|
...report_data.filter(
|
||||||
|
(entry) => entry.preference === "TEXT",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
report_subject = "TEXT Preference";
|
||||||
|
}}>Text</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<button class="styled" onclick={() => window.print()}>Print</button>
|
<button class="styled" onclick={() => window.print()}>Print</button>
|
||||||
@@ -35,35 +55,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="90"><h1>{prefix.name} - Report - {report_subject}</h1></th>
|
<th colspan="90"
|
||||||
</tr>
|
><h1>{prefix.name} - Report - {report_subject}</h1></th
|
||||||
<tr>
|
>
|
||||||
<th>Winner Name</th>
|
</tr>
|
||||||
<th>Phone Number</th>
|
<tr>
|
||||||
<th>Basket ID</th>
|
<th>Winner Name</th>
|
||||||
<th>Ticket #</th>
|
<th>Phone Number</th>
|
||||||
<th>Description</th>
|
<th>Basket ID</th>
|
||||||
</tr>
|
<th>Ticket #</th>
|
||||||
</thead>
|
<th>Description</th>
|
||||||
<tbody>
|
</tr>
|
||||||
{#each show_data as report_entry}
|
</thead>
|
||||||
<tr>
|
<tbody>
|
||||||
<td>{report_entry.winner_name}</td>
|
{#each show_data as report_entry}
|
||||||
<td>{report_entry.phone_number}</td>
|
<tr>
|
||||||
<td>{report_entry.b_id}</td>
|
<td>{report_entry.winner_name}</td>
|
||||||
<td>{report_entry.winning_ticket}</td>
|
<td>{report_entry.phone_number}</td>
|
||||||
<td>{report_entry.description}</td>
|
<td>{report_entry.b_id}</td>
|
||||||
</tr>
|
<td>{report_entry.winning_ticket}</td>
|
||||||
{/each}
|
<td>{report_entry.description}</td>
|
||||||
</tbody>
|
</tr>
|
||||||
<tfoot>
|
{/each}
|
||||||
<tr>
|
</tbody>
|
||||||
<td colspan="3">{env.PUBLIC_TAM3_VENUE || ""}</td>
|
<tfoot>
|
||||||
<td colspan="2" style="text-align: right">TAM3 by Dilan Gilluly</td>
|
<tr>
|
||||||
</tr>
|
<td colspan="3">{env.PUBLIC_TAM3_VENUE || ""}</td>
|
||||||
</tfoot>
|
<td colspan="2" style="text-align: right">TAM3 by Dilan Gilluly</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -76,7 +98,6 @@
|
|||||||
border: solid 1px black;
|
border: solid 1px black;
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
table tbody tr:nth-child(2n) {
|
table tbody tr:nth-child(2n) {
|
||||||
background-color: #dddddd;
|
background-color: #dddddd;
|
||||||
@@ -92,4 +113,4 @@
|
|||||||
margin: 0.25in;
|
margin: 0.25in;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
248
webapp/src/routes/search/tickets/+page.svelte
Normal file
248
webapp/src/routes/search/tickets/+page.svelte
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<script>
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
import SearchHeader from "$lib/components/SearchHeader.svelte";
|
||||||
|
import { focusElement } from "$lib/focusElement.js";
|
||||||
|
|
||||||
|
let searchForm = $state({
|
||||||
|
first_name: "",
|
||||||
|
last_name: "",
|
||||||
|
phone_number: "",
|
||||||
|
});
|
||||||
|
let current_idx = $state(0);
|
||||||
|
let next_idx = $derived(current_idx + 1);
|
||||||
|
let prev_idx = $derived(current_idx - 1);
|
||||||
|
let current_tickets = $state([]);
|
||||||
|
let copy_buffer = $state({
|
||||||
|
prefix: "",
|
||||||
|
t_id: 0,
|
||||||
|
first_name: "",
|
||||||
|
last_name: "",
|
||||||
|
phone_number: "",
|
||||||
|
preference: "CALL",
|
||||||
|
changed: true,
|
||||||
|
});
|
||||||
|
let headerHeight = $state();
|
||||||
|
|
||||||
|
function changeFocus(idx) {
|
||||||
|
const focusFn = document.getElementById(`${idx}_fn`);
|
||||||
|
if (focusFn) {
|
||||||
|
focusFn.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const functions = {
|
||||||
|
refreshPage: async () => {
|
||||||
|
if (
|
||||||
|
current_tickets.filter((ticket) => ticket.changed === true)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
|
functions.saveAll();
|
||||||
|
}
|
||||||
|
const searchParams = new URLSearchParams({ ...searchForm });
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/search/tickets?${searchParams.toString()}`,
|
||||||
|
);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
current_tickets = [...data];
|
||||||
|
setTimeout(() => changeFocus(0), 100);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prevPage: () => {
|
||||||
|
const diff = current_tickets.length;
|
||||||
|
pagerForm.id_from = pagerForm.id_from - diff;
|
||||||
|
pagerForm.id_to = pagerForm.id_to - diff;
|
||||||
|
functions.refreshPage();
|
||||||
|
},
|
||||||
|
nextPage: () => {
|
||||||
|
const diff = current_tickets.length;
|
||||||
|
pagerForm.id_from = pagerForm.id_from + diff;
|
||||||
|
pagerForm.id_to = pagerForm.id_to + diff;
|
||||||
|
functions.refreshPage();
|
||||||
|
},
|
||||||
|
duplicateDown: () => {
|
||||||
|
if (current_tickets[next_idx]) {
|
||||||
|
current_tickets[next_idx] = {
|
||||||
|
...current_tickets[current_idx],
|
||||||
|
prefix: current_tickets[next_idx].prefix,
|
||||||
|
t_id: current_tickets[next_idx].t_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
|
changeFocus(next_idx);
|
||||||
|
} else {
|
||||||
|
changeFocus(current_idx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
duplicateUp: () => {
|
||||||
|
if (prev_idx >= 0) {
|
||||||
|
current_tickets[prev_idx] = {
|
||||||
|
...current_tickets[current_idx],
|
||||||
|
prefix: current_tickets[prev_idx].prefix,
|
||||||
|
t_id: current_tickets[prev_idx].t_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
|
changeFocus(prev_idx);
|
||||||
|
} else {
|
||||||
|
changeFocus(current_idx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gotoNext: () => {
|
||||||
|
if (current_tickets[next_idx]) {
|
||||||
|
changeFocus(next_idx);
|
||||||
|
} else {
|
||||||
|
changeFocus(current_idx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gotoPrev: () => {
|
||||||
|
if (prev_idx >= 0) {
|
||||||
|
changeFocus(prev_idx);
|
||||||
|
} else {
|
||||||
|
changeFocus(current_idx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
copy: () => {
|
||||||
|
copy_buffer = { ...current_tickets[current_idx] };
|
||||||
|
changeFocus(current_idx);
|
||||||
|
},
|
||||||
|
paste: () => {
|
||||||
|
current_tickets[current_idx] = {
|
||||||
|
...copy_buffer,
|
||||||
|
prefix: current_tickets[current_idx].prefix,
|
||||||
|
t_id: current_tickets[current_idx].t_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
|
changeFocus(current_idx);
|
||||||
|
},
|
||||||
|
saveAll: async () => {
|
||||||
|
const to_save = current_tickets.filter(
|
||||||
|
(ticket) => ticket.changed === true,
|
||||||
|
);
|
||||||
|
const res = await fetch(`/api/tickets`, {
|
||||||
|
body: JSON.stringify(to_save),
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
for (let ticket of current_tickets) {
|
||||||
|
ticket.changed = false;
|
||||||
|
}
|
||||||
|
changeFocus(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
document.title = `Ticket Search`;
|
||||||
|
window.addEventListener("beforeunload", function (e) {
|
||||||
|
if (
|
||||||
|
current_tickets.filter((ticket) => ticket.changed === true)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>Ticket Search</h1>
|
||||||
|
<SearchHeader {functions} bind:searchForm bind:headerHeight />
|
||||||
|
<table>
|
||||||
|
<thead style="top: {headerHeight + 2}px">
|
||||||
|
<tr>
|
||||||
|
<th>Prefix</th>
|
||||||
|
<th style="width: 12ch">Ticket ID</th>
|
||||||
|
<th>First Name</th>
|
||||||
|
<th>Last Name</th>
|
||||||
|
<th>Phone Number</th>
|
||||||
|
<th>Preference</th>
|
||||||
|
<th>Changed</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each current_tickets as ticket, idx}
|
||||||
|
<tr onfocusin={() => (current_idx = idx)}>
|
||||||
|
<td>{ticket.prefix}</td>
|
||||||
|
<td>{ticket.t_id}</td>
|
||||||
|
<td
|
||||||
|
><input
|
||||||
|
id="{idx}_fn"
|
||||||
|
type="text"
|
||||||
|
bind:value={ticket.first_name}
|
||||||
|
onfocus={focusElement}
|
||||||
|
onchange={() => (ticket.changed = true)}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><input
|
||||||
|
id="{idx}_ln"
|
||||||
|
type="text"
|
||||||
|
bind:value={ticket.last_name}
|
||||||
|
onfocus={focusElement}
|
||||||
|
onchange={() => (ticket.changed = true)}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><input
|
||||||
|
id="{idx}_pn"
|
||||||
|
type="text"
|
||||||
|
bind:value={ticket.phone_number}
|
||||||
|
onfocus={focusElement}
|
||||||
|
onchange={() => (ticket.changed = true)}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><select
|
||||||
|
id="{idx}_pr"
|
||||||
|
style="width: 100%"
|
||||||
|
bind:value={ticket.preference}
|
||||||
|
onfocus={focusElement}
|
||||||
|
onchange={() => (ticket.changed = true)}
|
||||||
|
>
|
||||||
|
<option value="CALL">Call</option>
|
||||||
|
<option value="TEXT">Text</option>
|
||||||
|
</select></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><button
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={() => (ticket.changed = !ticket.changed)}
|
||||||
|
>{ticket.changed ? "Y" : "N"}</button
|
||||||
|
></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
thead {
|
||||||
|
background-color: #ffffff;
|
||||||
|
position: sticky;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
border: solid 1px #000000;
|
||||||
|
}
|
||||||
|
tbody tr:nth-child(2n) {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
}
|
||||||
|
tbody tr:focus-within td:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
border-top: solid 1px;
|
||||||
|
border-bottom: solid 1px;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
background: transparent;
|
||||||
|
border: solid 1px #000000;
|
||||||
|
}
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,27 +1,39 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/environment';
|
import { browser } from "$app/environment";
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
|
|
||||||
let stagingSettings = $state({...data.settings});
|
function copySettings() {
|
||||||
let status = $state("")
|
return { ...data.settings };
|
||||||
|
}
|
||||||
|
let stagingSettings = $state(copySettings());
|
||||||
|
let status = $state("");
|
||||||
|
|
||||||
async function saveChanges() {
|
async function saveChanges() {
|
||||||
const res = await fetch('/api/settings', {method: 'POST', body: JSON.stringify(stagingSettings), headers: {'Content-Type': 'application/json'}});
|
const res = await fetch("/api/settings", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(stagingSettings),
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
status = "Changes saved successfully";
|
status = "Changes saved successfully";
|
||||||
setTimeout(() => {status = ""}, 5000);
|
setTimeout(() => {
|
||||||
|
status = "";
|
||||||
|
}, 5000);
|
||||||
} else {
|
} else {
|
||||||
status = "Error saving changes, check config file path, make sure folder exists";
|
status =
|
||||||
setTimeout(() => {status = ""}, 5000);
|
"Error saving changes, check config file path, make sure folder exists";
|
||||||
|
setTimeout(() => {
|
||||||
|
status = "";
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelChanges() {
|
function cancelChanges() {
|
||||||
stagingSettings = {...data.settings};
|
stagingSettings = { ...data.settings };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
document.title = "TAM3 - Settings"
|
document.title = "TAM3 - Settings";
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -31,11 +43,16 @@
|
|||||||
<h2>Remote Server</h2>
|
<h2>Remote Server</h2>
|
||||||
<div><strong>Address:</strong></div>
|
<div><strong>Address:</strong></div>
|
||||||
<div><em>For example: https://ip_or_hostname:8443</em></div>
|
<div><em>For example: https://ip_or_hostname:8443</em></div>
|
||||||
<div><input type="text" bind:value={stagingSettings.TAM3_REMOTE}></div>
|
<div><input type="text" bind:value={stagingSettings.TAM3_REMOTE} /></div>
|
||||||
<div><strong>API Key:</strong></div>
|
<div><strong>API Key:</strong></div>
|
||||||
<div class="flex-row">
|
<div class="flex-row">
|
||||||
<input type="password" bind:value={stagingSettings.TAM3_REMOTE_KEY}>
|
<input type="password" bind:value={stagingSettings.TAM3_REMOTE_KEY} />
|
||||||
<button class="styled" onclick={() => {stagingSettings.TAM3_REMOTE_KEY = ""}}>Clear</button>
|
<button
|
||||||
|
class="styled"
|
||||||
|
onclick={() => {
|
||||||
|
stagingSettings.TAM3_REMOTE_KEY = "";
|
||||||
|
}}>Clear</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -50,4 +67,4 @@
|
|||||||
|
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<div>{status}</div>
|
<div>{status}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
131
webapp/src/routes/sheets/+page.svelte
Normal file
131
webapp/src/routes/sheets/+page.svelte
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<script>
|
||||||
|
let formData = $state({ startNumber: 0, endNumber: 0, perPage: 20 });
|
||||||
|
let currentRows = $state([]);
|
||||||
|
|
||||||
|
function selectOnFocus(e) {
|
||||||
|
e.target.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadRows() {
|
||||||
|
currentRows = ["Loading"]
|
||||||
|
setTimeout(() => {
|
||||||
|
currentRows = [];
|
||||||
|
for (let i = formData.startNumber; i <= formData.endNumber; i++) {
|
||||||
|
currentRows = [...currentRows, i];
|
||||||
|
}
|
||||||
|
}, 1)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Print Ticket Sheets</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<div id="header">
|
||||||
|
<h1>Print Ticket Sheets</h1>
|
||||||
|
<div class="flex-row-space">
|
||||||
|
<div class="flex-row">
|
||||||
|
<div>
|
||||||
|
<div>Start Number</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
onfocus={selectOnFocus}
|
||||||
|
bind:value={formData.startNumber}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>Ending Number</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
onfocus={selectOnFocus}
|
||||||
|
bind:value={formData.endNumber}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>Per Page</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
onfocus={selectOnFocus}
|
||||||
|
bind:value={formData.perPage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="styled" onclick={loadRows}>Load</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row">
|
||||||
|
<button class="styled" onclick={() => window.print()}>Print</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table id="main_table">
|
||||||
|
<colgroup>
|
||||||
|
<col style="width: 15%" />
|
||||||
|
<col style="width: 40%" />
|
||||||
|
<col style="width: 35%" />
|
||||||
|
<col style="width: 10%" />
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Ticket #</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Phone Number</th>
|
||||||
|
<th>Text?</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody style="height: 100%">
|
||||||
|
{#each currentRows as row, idx}
|
||||||
|
{#if idx !== 0 && idx % formData.perPage === 0}
|
||||||
|
<tr class="pagebreak"></tr>
|
||||||
|
{/if}
|
||||||
|
<tr>
|
||||||
|
<td><strong>{row}</strong></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#main_table {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: left;
|
||||||
|
table-layout: fixed;
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: solid black 1px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
#header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main_table {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main_table tbody {
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main_table tbody tr {
|
||||||
|
height: 32pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main_table tbody tr.pagebreak {
|
||||||
|
break-after: page;
|
||||||
|
}
|
||||||
|
|
||||||
|
@page:after {
|
||||||
|
content: "My Text";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/environment';
|
import { browser } from "$app/environment";
|
||||||
import FormHeader from '$lib/components/FormHeader.svelte';
|
import FormHeader from "$lib/components/FormHeader.svelte";
|
||||||
import { focusElement } from '$lib/focusElement.js';
|
import { focusElement } from "$lib/focusElement.js";
|
||||||
|
|
||||||
const { data } = $props();
|
const { data } = $props();
|
||||||
let prefix = {...data.prefix};
|
const prefix = $derived(data.prefix);
|
||||||
let pagerForm = $state({id_from: 0, id_to: 0});
|
let pagerForm = $state({ id_from: 0, id_to: 0 });
|
||||||
let current_idx = $state(0);
|
let current_idx = $state(0);
|
||||||
let next_idx = $derived(current_idx+1);
|
let next_idx = $derived(current_idx + 1);
|
||||||
let prev_idx = $derived(current_idx-1);
|
let prev_idx = $derived(current_idx - 1);
|
||||||
let current_tickets = $state([]);
|
let current_tickets = $state([]);
|
||||||
let copy_buffer = $state({prefix: prefix.name, t_id: 0, first_name: "", last_name: "", phone_number: "", preference: "CALL", changed: true});
|
let copy_buffer = $state({});
|
||||||
let headerHeight = $state();
|
let headerHeight = $state();
|
||||||
|
|
||||||
function changeFocus(idx) {
|
function changeFocus(idx) {
|
||||||
@@ -22,42 +22,55 @@
|
|||||||
|
|
||||||
const functions = {
|
const functions = {
|
||||||
refreshPage: async () => {
|
refreshPage: async () => {
|
||||||
if (current_tickets.filter(ticket => ticket.changed === true).length > 0) {
|
if (
|
||||||
|
current_tickets.filter((ticket) => ticket.changed === true)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
functions.saveAll();
|
functions.saveAll();
|
||||||
};
|
}
|
||||||
const res = await fetch(`/api/tickets/${prefix.name}/${pagerForm.id_from}/${pagerForm.id_to}`);
|
const res = await fetch(
|
||||||
|
`/api/tickets/${prefix.name}/${pagerForm.id_from}/${pagerForm.id_to}`,
|
||||||
|
);
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
current_tickets = [...data];
|
current_tickets = [...data];
|
||||||
setTimeout(() => changeFocus(0), 100);
|
setTimeout(() => changeFocus(0), 100);
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
prevPage: () => {
|
prevPage: () => {
|
||||||
const diff = current_tickets.length;
|
const diff = current_tickets.length;
|
||||||
pagerForm.id_from = pagerForm.id_from - diff;
|
pagerForm.id_from = pagerForm.id_from - diff;
|
||||||
pagerForm.id_to = pagerForm.id_to - diff;
|
pagerForm.id_to = pagerForm.id_to - diff;
|
||||||
functions.refreshPage()
|
functions.refreshPage();
|
||||||
},
|
},
|
||||||
nextPage: () => {
|
nextPage: () => {
|
||||||
const diff = current_tickets.length;
|
const diff = current_tickets.length;
|
||||||
pagerForm.id_from = pagerForm.id_from + diff;
|
pagerForm.id_from = pagerForm.id_from + diff;
|
||||||
pagerForm.id_to = pagerForm.id_to + diff;
|
pagerForm.id_to = pagerForm.id_to + diff;
|
||||||
functions.refreshPage()
|
functions.refreshPage();
|
||||||
},
|
},
|
||||||
duplicateDown: () => {
|
duplicateDown: () => {
|
||||||
if (current_tickets[next_idx]) {
|
if (current_tickets[next_idx]) {
|
||||||
current_tickets[next_idx] = {...current_tickets[current_idx], t_id: current_tickets[next_idx].t_id, changed: true};
|
current_tickets[next_idx] = {
|
||||||
|
...current_tickets[current_idx],
|
||||||
|
t_id: current_tickets[next_idx].t_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
changeFocus(next_idx);
|
changeFocus(next_idx);
|
||||||
} else {
|
} else {
|
||||||
changeFocus(current_idx)
|
changeFocus(current_idx);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
duplicateUp: () => {
|
duplicateUp: () => {
|
||||||
if (prev_idx >= 0) {
|
if (prev_idx >= 0) {
|
||||||
current_tickets[prev_idx] = {...current_tickets[current_idx], t_id: current_tickets[prev_idx].t_id, changed: true};
|
current_tickets[prev_idx] = {
|
||||||
|
...current_tickets[current_idx],
|
||||||
|
t_id: current_tickets[prev_idx].t_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
changeFocus(prev_idx);
|
changeFocus(prev_idx);
|
||||||
} else {
|
} else {
|
||||||
changeFocus(current_idx)
|
changeFocus(current_idx);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
gotoNext: () => {
|
gotoNext: () => {
|
||||||
@@ -75,37 +88,58 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
copy: () => {
|
copy: () => {
|
||||||
copy_buffer = {...current_tickets[current_idx]};
|
copy_buffer = { ...current_tickets[current_idx] };
|
||||||
changeFocus(current_idx);
|
changeFocus(current_idx);
|
||||||
|
disablePaste = false;
|
||||||
},
|
},
|
||||||
paste: () => {
|
paste: () => {
|
||||||
current_tickets[current_idx] = {...copy_buffer, t_id: current_tickets[current_idx].t_id, changed: true};
|
if (Object.keys(copy_buffer).length !== 0) {
|
||||||
|
current_tickets[current_idx] = {
|
||||||
|
...copy_buffer,
|
||||||
|
t_id: current_tickets[current_idx].t_id,
|
||||||
|
changed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
changeFocus(current_idx);
|
changeFocus(current_idx);
|
||||||
},
|
},
|
||||||
saveAll: async () => {
|
saveAll: async () => {
|
||||||
const to_save = current_tickets.filter((ticket) => ticket.changed === true);
|
const to_save = current_tickets.filter(
|
||||||
const res = await fetch(`/api/tickets`, {body: JSON.stringify(to_save), method: 'POST', headers: {'Content-Type': 'application/json'}});
|
(ticket) => ticket.changed === true,
|
||||||
|
);
|
||||||
|
const res = await fetch(`/api/tickets`, {
|
||||||
|
body: JSON.stringify(to_save),
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
for (let ticket of current_tickets) {ticket.changed = false};
|
for (let ticket of current_tickets) {
|
||||||
|
ticket.changed = false;
|
||||||
|
}
|
||||||
changeFocus(0);
|
changeFocus(0);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
document.title = `${prefix.name} Ticket Entry`;
|
window.addEventListener("beforeunload", function (e) {
|
||||||
window.addEventListener("beforeunload", function(e) {
|
if (
|
||||||
if (current_tickets.filter(ticket => ticket.changed === true).length > 0) {
|
current_tickets.filter((ticket) => ticket.changed === true)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{prefix.name} Ticket Entry</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<h1>{prefix.name} Ticket Entry</h1>
|
<h1>{prefix.name} Ticket Entry</h1>
|
||||||
<FormHeader {prefix} {functions} bind:pagerForm bind:headerHeight />
|
<FormHeader {prefix} {functions} bind:pagerForm bind:headerHeight />
|
||||||
<table>
|
<table>
|
||||||
<thead style="top: {headerHeight+2}px">
|
<thead style="top: {headerHeight + 2}px">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 12ch">Ticket ID</th>
|
<th style="width: 12ch">Ticket ID</th>
|
||||||
<th>First Name</th>
|
<th>First Name</th>
|
||||||
@@ -117,17 +151,55 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each current_tickets as ticket, idx}
|
{#each current_tickets as ticket, idx}
|
||||||
<tr onfocusin={() => current_idx = idx}>
|
<tr onfocusin={() => (current_idx = idx)}>
|
||||||
<td>{ticket.t_id}</td>
|
<td>{ticket.t_id}</td>
|
||||||
<td><input id="{idx}_fn" type="text" bind:value={ticket.first_name} onfocus={focusElement} onchange={() => ticket.changed = true}></td>
|
<td
|
||||||
<td><input id="{idx}_ln" type="text" bind:value={ticket.last_name} onfocus={focusElement} onchange={() => ticket.changed = true}></td>
|
><input
|
||||||
<td><input id="{idx}_pn" type="text" bind:value={ticket.phone_number} onfocus={focusElement} onchange={() => ticket.changed = true}></td>
|
id="{idx}_fn"
|
||||||
<td><select id="{idx}_pr" style="width: 100%" bind:value={ticket.preference} onfocus={focusElement} onchange={() => ticket.changed = true}>
|
type="text"
|
||||||
<option value="CALL">Call</option>
|
bind:value={ticket.first_name}
|
||||||
<option value="TEXT">Text</option>
|
onfocus={focusElement}
|
||||||
</select></td>
|
onchange={() => (ticket.changed = true)}
|
||||||
<td><button tabindex="-1" onclick={() => ticket.changed = !ticket.changed}>{ticket.changed ? "Y" : "N"}</button></td>
|
/></td
|
||||||
</tr>
|
>
|
||||||
|
<td
|
||||||
|
><input
|
||||||
|
id="{idx}_ln"
|
||||||
|
type="text"
|
||||||
|
bind:value={ticket.last_name}
|
||||||
|
onfocus={focusElement}
|
||||||
|
onchange={() => (ticket.changed = true)}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><input
|
||||||
|
id="{idx}_pn"
|
||||||
|
type="text"
|
||||||
|
bind:value={ticket.phone_number}
|
||||||
|
onfocus={focusElement}
|
||||||
|
onchange={() => (ticket.changed = true)}
|
||||||
|
/></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><select
|
||||||
|
id="{idx}_pr"
|
||||||
|
style="width: 100%"
|
||||||
|
bind:value={ticket.preference}
|
||||||
|
onfocus={focusElement}
|
||||||
|
onchange={() => (ticket.changed = true)}
|
||||||
|
>
|
||||||
|
<option value="CALL">Call</option>
|
||||||
|
<option value="TEXT">Text</option>
|
||||||
|
</select></td
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
><button
|
||||||
|
tabindex="-1"
|
||||||
|
onclick={() => (ticket.changed = !ticket.changed)}
|
||||||
|
>{ticket.changed ? "Y" : "N"}</button
|
||||||
|
></td
|
||||||
|
>
|
||||||
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -156,10 +228,11 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
border: solid 1px #000000;
|
border: solid 1px #000000;
|
||||||
}
|
}
|
||||||
input, button {
|
input,
|
||||||
|
button {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user