Compare commits

5 Commits

Author SHA1 Message Date
5d24b80e26 (fix): prefixes 2025-10-21 22:51:31 -04:00
32263ad6c6 MM test correction 2025-10-21 22:44:34 -04:00
382eddf8ea MM revamp, save prompt, upload fix 2025-10-21 22:27:13 -04:00
0b0ae291d0 (change): MM revamp, scroll behavior 2025-10-19 21:35:18 -04:00
d7523f3e67 0.1.1 2025-10-19 21:00:31 -04:00
13 changed files with 84 additions and 33 deletions

View File

@@ -9,9 +9,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 NODE_TLS_REJECT_UNAUTHORIZED=0 -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:0.1.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 NODE_TLS_REJECT_UNAUTHORIZED=0 -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:0.1.1
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 NODE_TLS_REJECT_UNAUTHORIZED=0 -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:0.1.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 NODE_TLS_REJECT_UNAUTHORIZED=0 -e PUBLIC_TAM3_VENUE="$venuename" -p 127.0.0.1:8300:3000 docker.io/dbob16/tam3-webclient:0.1.1
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 +19,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.1.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:0.1.1
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.1.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:0.1.1
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."

View File

@@ -1,6 +1,6 @@
services: services:
tam3-db: tam3-db:
image: docker.io/dbob16/tam3-db:0.1.0 image: docker.io/dbob16/tam3-db:0.1.1
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.1.0 image: docker.io/dbob16/tam3-api:0.1.1
restart: always restart: always
environment: environment:
TAM3_DATA_PATH: /data TAM3_DATA_PATH: /data

0
deployment/remote_server_offline_images/server-load.sh Normal file → Executable file
View File

View File

@@ -1,6 +1,6 @@
services: services:
tam3-db: tam3-db:
image: docker.io/dbob16/tam3-db:0.1.0 image: docker.io/dbob16/tam3-db:0.1.1
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.1.0 image: docker.io/dbob16/tam3-api:0.1.1
restart: always restart: always
environment: environment:
TAM3_DATA_PATH: /data TAM3_DATA_PATH: /data

View File

@@ -7,6 +7,8 @@ COPY . .
RUN npm install && npm run build RUN npm install && npm run build
ENV DATABASE_URL=file:/data/local.db ENV DATABASE_URL=file:/data/local.db
ENV BODY_SIZE_LIMIT=100M
ENV NODE_TLS_REJECT_UNAUTHORIZED=0
COPY deploy/start-server.sh start-server.sh COPY deploy/start-server.sh start-server.sh

View File

@@ -28,7 +28,12 @@
<input type="number" bind:value={pagerForm.id_from}> <input type="number" 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" bind:value={pagerForm.id_to}>
<button class="styled" onclick={functions.refreshPage}>Refresh</button> <button class="styled" onclick={() => {
if (Math.abs(pagerForm.id_to - pagerForm.id_from) > 800) {
pagerForm.id_to = pagerForm.id_from + 799;
}
setTimeout(functions.refreshPage, 50);
}}>Refresh</button>
</div> </div>
<div class="flex-row"> <div class="flex-row">
<button class="styled" title="Alt + B" tabindex="-1" onclick={functions.prevPage}>Prev Page</button> <button class="styled" title="Alt + B" tabindex="-1" onclick={functions.prevPage}>Prev Page</button>

View File

@@ -15,6 +15,15 @@ a.styled, button.styled {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
.p025 {
padding: 0.25rem;
}
.active {
border: 1px solid #000000;
border-radius: 0.13rem;
}
a.styled:hover, button.styled:hover { a.styled:hover, button.styled:hover {
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;

View File

@@ -5,8 +5,8 @@
import favicon from "$lib/assets/favicon.svg" import favicon from "$lib/assets/favicon.svg"
let { data } = $props(); let { data } = $props();
const all_prefixes = [...data.prefixes];
let prefix_name = $state(""); let prefix_name = $state("");
let all_prefixes = $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";
@@ -19,7 +19,6 @@
}) })
if (browser) { if (browser) {
all_prefixes = [...data.prefixes];
document.title = `${venue} - Main Menu`; document.title = `${venue} - Main Menu`;
hotkeys.filter = function(event) {return true}; hotkeys.filter = function(event) {return true};
hotkeys('alt+a', function(event) {event.preventDefault(); admin_mode = !admin_mode; return false;}); hotkeys('alt+a', function(event) {event.preventDefault(); admin_mode = !admin_mode; return false;});
@@ -36,18 +35,24 @@
<img src="{favicon}" alt="TAM3 Icon - Red ticket with TAM3 on it" class="icon"> <img src="{favicon}" alt="TAM3 Icon - Red ticket with TAM3 on it" class="icon">
<h1>{venue} - Main Menu</h1> <h1>{venue} - Main Menu</h1>
</div> </div>
{#if all_prefixes.length > 0}
<div class="universal-reports flex-row tb-margin"> <div class="universal-reports flex-row tb-margin">
<a href="/counts" target="_blank" class="styled">Counts</a> <a href="/counts" target="_blank" class="styled">Counts</a>
</div> </div>
<div> <div>
<h2>Select Prefix:</h2> <h2>Current Prefix: {current_prefix.name}</h2>
</div> </div>
<div class="prefix-selector"> <div class="change_title">
<select style="width: 100%; box-sizing: border-box;" bind:value={prefix_name}> <h2>Change Prefix:</h2>
</div>
<div class="prefix-selector flex-row">
{#each all_prefixes as prefix} {#each all_prefixes as prefix}
<option value={prefix.name}>{prefix.name}</option> <div class="{prefix.color} p025{prefix.name === prefix_name ? " active" : ""}">
<button class="styled" onclick={() => {
prefix_name = prefix.name;
}}>{prefix.name}</button>
</div>
{/each} {/each}
</select>
</div> </div>
<div><h2>Forms:</h2></div> <div><h2>Forms:</h2></div>
<div class="flex-row {current_prefix.color}"> <div class="flex-row {current_prefix.color}">
@@ -60,14 +65,17 @@
<a href="/reports/byname/{current_prefix.name}/" target="_blank" class="styled">By Name</a> <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> <a href="/reports/bybasket/{current_prefix.name}/" target="_blank" class="styled">By Basket ID</a>
</div> </div>
{#if admin_mode} {:else}
<div><h2>Admin Mode:</h2></div> <p>There aren't any prefixes available, please create them.</p>
<div class="flex-row {current_prefix.color}">
<a href="/prefixes" target="_blank" class="styled">Prefix Editor</a>
<a href="/backuprestore" target="_blank" class="styled">Backup/Restore</a>
</div>
{/if} {/if}
</div> </div>
{#if admin_mode}
<div><h2>Admin Mode:</h2></div>
<div class="flex-row">
<a href="/prefixes" target="_blank" class="styled">Prefix Editor</a>
<a href="/backuprestore" target="_blank" class="styled">Backup/Restore</a>
</div>
{/if}
<div class="status tb-margin"> <div class="status tb-margin">
{data.status} {data.status}

View File

@@ -1,6 +1,15 @@
import { env } from "$env/dynamic/private"; import { env } from "$env/dynamic/private";
import { db } from "$lib/server/db"; import { db } from "$lib/server/db";
import { prefixes, tickets, baskets } from "$lib/server/db/schema"; import { prefixes, tickets, baskets } from "$lib/server/db/schema";
import { sql } from "drizzle-orm";
function chunkArray(arr, chunkSize) {
const chunks = [];
for (let i = 0; i < arr.length; i += chunkSize) {
chunks.push(arr.slice(i, i + chunkSize));
}
return chunks;
}
export async function GET() { export async function GET() {
if (env.TAM3_REMOTE) { if (env.TAM3_REMOTE) {
@@ -20,16 +29,16 @@ export async function GET() {
export async function POST({ request }) { export async function POST({ request }) {
const req = await request.json(); const req = await request.json();
for (let prefix of req.prefixes) { for (let prefixChunk of chunkArray(req.prefixes, 300)) {
await db.insert(prefixes).values({name: prefix.name, color: prefix.color, weight: prefix.weight}).onConflictDoUpdate({target: prefixes.name, set: {color: prefix.color, weight: prefix.weight}}); await db.insert(prefixes).values(prefixChunk).onConflictDoUpdate({target: prefixes.name, set: {color: sql`excluded.color`, weight: sql`excluded.weight`}});
}; };
for (let ticket of req.tickets) { for (let ticketChunk of chunkArray(req.tickets, 300)) {
await db.insert(tickets).values({prefix: ticket.prefix, t_id: ticket.t_id, first_name: ticket.first_name, last_name: ticket.last_name, phone_number: ticket.phone_number, preference: ticket.preference}) await db.insert(tickets).values(ticketChunk)
.onConflictDoUpdate({target: [tickets.prefix, tickets.t_id], set: {first_name: ticket.first_name, last_name: ticket.last_name, phone_number: ticket.phone_number, preference: ticket.preference}}); .onConflictDoUpdate({target: [tickets.prefix, tickets.t_id], set: {first_name: sql`excluded.first_name`, last_name: sql`excluded.last_name`, phone_number: sql`excluded.phone_number`, preference: sql`excluded.preference`}});
}; };
for (let basket of req.baskets) { for (let basketChunk of chunkArray(req.baskets, 300)) {
await db.insert(baskets).values({prefix: basket.prefix, b_id: basket.b_id, description: basket.description, donors: basket.donors, winning_ticket: basket.winning_ticket}) await db.insert(baskets).values(basketChunk)
.onConflictDoUpdate({target: [baskets.prefix, baskets.b_id], set: {description: basket.description, donors: basket.donors, winning_ticket: basket.winning_ticket}}) .onConflictDoUpdate({target: [baskets.prefix, baskets.b_id], set: {description: sql`excluded.description`, donors: sql`excluded.donors`, winning_ticket: sql`excluded.winning_ticket`}})
}; };
if (env.TAM3_REMOTE) { if (env.TAM3_REMOTE) {
const res = await fetch(`${env.TAM3_REMOTE}/api/backuprestore/?api_key=${env.TAM3_REMOTE_KEY}`, { const res = await fetch(`${env.TAM3_REMOTE}/api/backuprestore/?api_key=${env.TAM3_REMOTE_KEY}`, {

View File

@@ -14,6 +14,7 @@
const focusDe = document.getElementById(`${idx}_de`); const focusDe = document.getElementById(`${idx}_de`);
if (focusDe) { if (focusDe) {
focusDe.select(); focusDe.select();
focusDe.scrollIntoView({block: "center"});
} }
} }
@@ -93,6 +94,11 @@
if (browser) { if (browser) {
document.title = `${prefix.name} Basket Entry` document.title = `${prefix.name} Basket Entry`
window.addEventListener("beforeunload", function(e) {
if (current_baskets.filter(basket => basket.changed === true).length > 0) {
e.preventDefault();
}
})
} }
</script> </script>

View File

@@ -13,6 +13,7 @@
const focusWt = document.getElementById(`${idx}_wt`); const focusWt = document.getElementById(`${idx}_wt`);
if (focusWt) { if (focusWt) {
focusWt.select(); focusWt.select();
focusWt.scrollIntoView({block: "center"});
} }
} }
@@ -92,6 +93,11 @@
if (browser) { if (browser) {
document.title = `${prefix.name} Drawing Form` document.title = `${prefix.name} Drawing Form`
window.addEventListener("beforeunload", function(e) {
if (current_drawings.filter(drawing => drawing.changed === true).length > 0) {
e.preventDefault();
}
});
} }
</script> </script>

View File

@@ -9,7 +9,7 @@
let report_subject = $state("All Preferences"); let report_subject = $state("All Preferences");
if (browser) { if (browser) {
document.title = `${prefix.name} Report By Name` document.title = `${prefix.name} Report By Basket ID`
} }
</script> </script>

View File

@@ -13,6 +13,7 @@
const focusFn = document.getElementById(`${idx}_fn`); const focusFn = document.getElementById(`${idx}_fn`);
if (focusFn) { if (focusFn) {
focusFn.select(); focusFn.select();
focusFn.scrollIntoView({block: "center"});
} }
} }
@@ -94,6 +95,11 @@
if (browser) { if (browser) {
document.title = `${prefix.name} Ticket Entry`; document.title = `${prefix.name} Ticket Entry`;
window.addEventListener("beforeunload", function(e) {
if (current_tickets.filter(ticket => ticket.changed === true).length > 0) {
e.preventDefault();
}
});
} }
</script> </script>