(add): Main branch, (change): property to method conversion

This commit is contained in:
dbob16 committed 2026-08-05 15:44:23 -04:00
1 parent 86a81e339c
commit 4869c119d0
7 files changed
+154 -116

No files matched your search

+69 -2
View File
@@ -2,6 +2,73 @@
This is Ticket Auction Manager. A project I (Dilan Gilluly) am working on as a hobby project. It's main scope is to manage in person penny socials or benefit auctions.
## Under Development
The remote server (api directory) is written in FastAPI and Python and the client (client directory) is written in Sveltekit.
This project is **NOT COMPLETE** and is under **active development**. Also I am not a programmer by profession so please forgive any beginner mistakes I make.
Features:
- **Forms**: Facilitates the entry of data throughout the platform. The main goal of this is to allow one to manage in-person benefit auctions for non-profit causes.
- **Ticket Form**: Enter the names and phone numbers of ticket purchasers, as well as their contact preference, which the default is controllable via the Settings screen.
- **Basket Form**: Optionally, basket/item descriptions can be added as well as who the donor(s) are for each one. The descriptions appear on the reports later on.
- **Drawing Form**: Use this form to enter the winning ticket numbers. The form automatically looks up if an entry exists when a field is changed and will popluate the information next to it if it does.
- **Reports**: Reports are automatically generated with one click to avoid line shifts or other issues which may arise during compilation.
- **By Name Report**: This report orders the lines by the last name of each winner, then first name, phone number, and finally basket number.
- **By Basket Report**: Orders winners by basket number.
- **Counts Report**: Displays counts of ticket sales by prefix as well as totals.
- **Settings**:
- **Settings section**: The Admin/settings section can be accessed on the main menu by pressing Alt(option)+A. The combination toggles it so if you want it to go away again, just press Alt(option)+A again.
- **Settings page**: Allows you to control the base options of the operation. Including remote mode (leave the Remote Server field blank for standalone(offline) mode or put in a remote server to connect to a server), remote port, TLS, default contact preference, and the name of the venue/benefit (appears on main menu as well as reports).
- **Auth Keys**: Allows you to manage auth keys if it's in remote mode. To do so, you need to know the auth password on the server.
- **Prefixes**: Allows you to add or change prefixes which are available on the main menu to be able to access the respective forms for each prefix. Note, **you need to add prefixes through this form after first installation of either client or server to be able to access forms and reports.**
- **Remote Mode**: Remote mode, which is configurable in the Settings screen, by putting in a remote server address/hostname, allows data to be synched across multiple computers for large scale operations.
## Cloning the repo
To clone the repo you just need to run the git clone command to clone it to a directory of your choosing. I generally try to push to a few different git providers just so that if one has an outage, it allows you to go with another one for the time being. Replace 'yourrepofolder' at the end with the folder/dir of your choosing.
Gitlab:
`git clone https://www.gitlab.com/Ticket-Auction-Manager/tam yourrepofolder`
Github:
`git clone https://www.github.com/Ticket-Auction-Manager/tam yourrepofolder`
My own git server:
`git clone https://git.dilangilluly.us/Ticket-Auction-Manager/tam yourrepofolder`
## Installing dependencies
Server:
(needs Python installed, and a virtual environment activated depending on OS distro and Python version)
```
cd api
pip install -r app/requirements.txt
```
Client:
(needs pnpm installed)
```
cd client
pnpm install
```
## Running dev instances
Server:
```
cd api
sh start_dev.sh
```
Client:
```
cd client
pnpm dev
```
+13 -30
View File
@@ -1,42 +1,25 @@
# sv
# Ticket Auction Manager (Client)
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
The contents of this directory of the Ticket Auction Manager repo pertains to just the client side. Which is a web-based client interface, allowing users to use Ticket Auction Manager.
## Creating a project
Due to dev resources already being in the main repo, I have decided to make this dedicated to users instead of developers.
If you're seeing this, you've probably already done this step. Congrats!
## Running TAM
```sh
# create a new project
npx sv create my-app
```
You have some choice when it comes to running TAM, using Docker or through a portable Node environment (coming soon!).
To recreate this project with the same configuration:
### Docker
```sh
# recreate this project
pnpm dlx sv@0.16.1 create --template minimal --no-types --add prettier eslint tailwindcss="plugins:none" sveltekit-adapter="adapter:node" drizzle="database:sqlite+sqlite:better-sqlite3" --install pnpm ./client
```
Docker images are available under the dbob16/tam-client repo within Docker Hub. To pull the latest image:
## Developing
`docker pull dbob16/tam-client:latest`
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
Then to run it:
```sh
npm run dev
`docker run -d --name tam-client --network host --restart unless-stopped -e host=127.0.0.1 -e port=3000 -v tam-data:/data dbob16/tam-client:latest`
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
It should then be available in a web browser at `http://localhost:3000` on the computer it is being run on. You can bookmark or make shortcuts to it for user convenience.
## Building
#### Port Conflict
To create a production version of your app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
If there is a conflict with the port when you go to start TAM, you can change the port by changing the number in the `-e port=3000` to another integer. For instance, if you have to change it to port 3001, you can change it to `-e port=3001` and then navigate to `http://localhost:3001` instead.
@@ -40,7 +40,7 @@
onclick={(e) => {
e.target.select();
}}
onchange={() => {
oninput={() => {
if (functions.pagerFromUpdate) functions.pagerFromUpdate();
}}
bind:value={pager.idFrom}
+19 -23
View File
@@ -27,11 +27,11 @@
let pager = $state({ idFrom: 0, idTo: 0 });
let items = $state([]);
let itemsLength = $derived.by(() => (items.length ? items.length : 0));
let itemsLength = $derived(items.length || 1);
let itemsBuffer = $derived(items.filter((i) => i.changed));
const functions = {
getPage: async () => {
functions.save();
async getPage(){
this.save();
if (pager.idFrom > pager.idTo) {
[pager.idFrom, pager.idTo] = [pager.idTo, pager.idFrom];
}
@@ -45,13 +45,9 @@
setTimeout(() => focusIdx(0));
},
pagerFromUpdate(){
if (itemsLength === 0) {
pager.idTo = pager.idFrom;
} else {
pager.idTo = pager.idFrom + (itemsLength - 1);
}
pager.idTo = pager.idFrom + (itemsLength - 1);
},
save: async () => {
async save(){
if (itemsBuffer.length > 0) {
const res = await fetch('/api/baskets', {
method: 'POST',
@@ -68,15 +64,15 @@
focusIdx(0);
}, 1);
},
prevPage: () => {
prevPage(){
((pager.idFrom -= itemsLength), (pager.idTo -= itemsLength));
functions.getPage();
this.getPage();
},
nextPage: () => {
nextPage(){
((pager.idFrom += itemsLength), (pager.idTo += itemsLength));
functions.getPage();
this.getPage();
},
nextLine: () => {
nextLine(){
if (items[nextIdx]) {
setTimeout(() => {
focusIdx(nextIdx);
@@ -87,7 +83,7 @@
}, 1);
}
},
prevLine: () => {
prevLine(){
if (curIdx > 0) {
setTimeout(() => {
focusIdx(prevIdx);
@@ -98,27 +94,27 @@
}, 1);
}
},
dupDown: () => {
dupDown(){
if (items[nextIdx]) {
const buffer = { ...items[curIdx] };
['prefix', 'b_id'].forEach((key) => delete buffer[key]);
items[nextIdx] = { ...items[nextIdx], ...buffer, changed: true };
functions.nextLine();
this.nextLine();
} else {
focusIdx(curIdx);
}
},
dupUp: () => {
dupUp(){
if (curIdx > 0) {
const buffer = { ...items[curIdx] };
['prefix', 'b_id'].forEach((key) => delete buffer[key]);
items[prevIdx] = { ...items[prevIdx], ...buffer, changed: true };
functions.prevLine();
this.prevLine();
} else {
focusIdx(curIdx);
}
},
copy: () => {
copy(){
if (items[curIdx]) {
const buffer = { ...items[curIdx] };
['prefix', 't_id'].forEach((key) => delete buffer[key]);
@@ -126,7 +122,7 @@
}
focusIdx(curIdx);
},
paste: () => {
paste(){
if (items[curIdx]) {
const buffer = JSON.parse(window.localStorage.getItem('tam-basket'));
items[curIdx] = { ...items[curIdx], ...buffer, changed: true };
@@ -186,7 +182,7 @@
type="text"
class="{iS.normal} w-full"
id="{idx}_first"
onchangecapture={() => (item.changed = true)}
oninput={() => (item.changed = true)}
bind:value={item.description}
/></td
>
@@ -195,7 +191,7 @@
type="text"
class="{iS.normal} w-full"
id="{idx}_second"
onchangecapture={() => (item.changed = true)}
oninput={() => (item.changed = true)}
bind:value={item.donors}
/></td
>
+20 -24
View File
@@ -27,11 +27,11 @@
let pager = $state({ idFrom: 0, idTo: 0 });
let items = $state([]);
let itemsLength = $derived.by(() => (items.length ? items.length : 0));
let itemsLength = $derived(items.length || 1);
let itemsBuffer = $derived(items.filter((i) => i.changed));
const functions = {
getPage: async () => {
functions.save();
async getPage(){
this.save();
if (pager.idFrom > pager.idTo) {
[pager.idFrom, pager.idTo] = [pager.idTo, pager.idFrom];
}
@@ -44,14 +44,7 @@
items = [...resData];
setTimeout(() => focusIdx(0));
},
pagerFromUpdate(){
if (itemsLength === 0) {
pager.idTo = pager.idFrom;
} else {
pager.idTo = pager.idFrom + (itemsLength - 1);
}
},
save: async () => {
async save(){
if (itemsBuffer.length > 0) {
const res = await fetch('/api/drawing', {
method: 'POST',
@@ -68,15 +61,18 @@
focusIdx(0);
}, 1);
},
prevPage: () => {
pagerFromUpdate(){
pager.idTo = pager.idFrom + (itemsLength - 1);
},
prevPage(){
((pager.idFrom -= itemsLength), (pager.idTo -= itemsLength));
functions.getPage();
this.getPage();
},
nextPage: () => {
nextPage(){
((pager.idFrom += itemsLength), (pager.idTo += itemsLength));
functions.getPage();
this.getPage();
},
nextLine: () => {
nextLine(){
if (items[nextIdx]) {
setTimeout(() => {
focusIdx(nextIdx);
@@ -87,7 +83,7 @@
}, 1);
}
},
prevLine: () => {
prevLine(){
if (curIdx > 0) {
setTimeout(() => {
focusIdx(prevIdx);
@@ -98,27 +94,27 @@
}, 1);
}
},
dupDown: () => {
dupDown(){
if (items[nextIdx]) {
const buffer = { ...items[curIdx] };
['prefix', 'b_id'].forEach((key) => delete buffer[key]);
items[nextIdx] = { ...items[nextIdx], ...buffer, changed: true };
functions.nextLine();
this.nextLine();
} else {
focusIdx(curIdx);
}
},
dupUp: () => {
dupUp(){
if (curIdx > 0) {
const buffer = { ...items[curIdx] };
['prefix', 'b_id'].forEach((key) => delete buffer[key]);
items[prevIdx] = { ...items[prevIdx], ...buffer, changed: true };
functions.prevLine();
this.prevLine();
} else {
focusIdx(curIdx);
}
},
copy: () => {
copy(){
if (items[curIdx]) {
const buffer = { ...items[curIdx] };
['prefix', 'b_id'].forEach((key) => delete buffer[key]);
@@ -126,7 +122,7 @@
}
focusIdx(curIdx);
},
paste: () => {
paste(){
if (items[curIdx]) {
const buffer = JSON.parse(window.localStorage.getItem('tam-drawing'));
items[curIdx] = { ...items[curIdx], ...buffer, changed: true };
@@ -187,7 +183,7 @@
type="number"
class="{iS.normal} w-full"
id="{idx}_first"
onchangecapture={async () => {
oninput={async () => {
item.changed = true;
const res = await fetch(`/api/tickets/${prefix.prefix}/${item.winning_ticket}`);
if (res.ok) {
+13 -13
View File
@@ -34,7 +34,7 @@
let items = $state([]);
let itemsBuffer = $derived(items.filter((i) => i.changed));
const functions = {
search: async () => {
async search(){
const searchParams = new URLSearchParams({ ...searchForm });
const res = await fetch(`/api/search/tickets?${searchParams.toString()}`);
if (res.ok) {
@@ -43,7 +43,7 @@
setTimeout(() => focusIdx(0), 1);
}
},
save: async () => {
async save(){
if (itemsBuffer.length > 0) {
const res = await fetch('/api/search/tickets', {
method: 'POST',
@@ -60,7 +60,7 @@
focusIdx(0);
}, 1);
},
nextLine: () => {
nextLine(){
if (items[nextIdx]) {
setTimeout(() => {
focusIdx(nextIdx);
@@ -71,7 +71,7 @@
}, 1);
}
},
prevLine: () => {
prevLine(){
if (curIdx > 0) {
setTimeout(() => {
focusIdx(prevIdx);
@@ -82,27 +82,27 @@
}, 1);
}
},
dupDown: () => {
dupDown(){
if (items[nextIdx]) {
const buffer = { ...items[curIdx] };
['prefix', 't_id'].forEach((key) => delete buffer[key]);
items[nextIdx] = { ...items[nextIdx], ...buffer, changed: true };
functions.nextLine();
this.nextLine();
} else {
focusIdx(curIdx);
}
},
dupUp: () => {
dupUp(){
if (curIdx > 0) {
const buffer = { ...items[curIdx] };
['prefix', 't_id'].forEach((key) => delete buffer[key]);
items[prevIdx] = { ...items[prevIdx], ...buffer, changed: true };
functions.prevLine();
this.prevLine();
} else {
focusIdx(curIdx);
}
},
copy: () => {
copy(){
if (items[curIdx]) {
const buffer = { ...items[curIdx] };
['prefix', 't_id'].forEach((key) => delete buffer[key]);
@@ -110,7 +110,7 @@
}
setTimeout(() => focusIdx(curIdx), 1);
},
paste: () => {
paste(){
if (items[curIdx]) {
const buffer = JSON.parse(window.localStorage.getItem('tam-ticket'));
items[curIdx] = { ...items[curIdx], ...buffer, changed: true };
@@ -171,7 +171,7 @@
type="text"
class="{iS.normal} w-full"
id="{idx}_first"
onchangecapture={() => (item.changed = true)}
oninput={() => (item.changed = true)}
bind:value={item.first_name}
/></td
>
@@ -180,7 +180,7 @@
type="text"
class="{iS.normal} w-full"
id="{idx}_second"
onchangecapture={() => (item.changed = true)}
oninput={() => (item.changed = true)}
bind:value={item.last_name}
/></td
>
@@ -189,7 +189,7 @@
type="text"
class="{iS.normal} w-full"
id="{idx}_third"
onchangecapture={() => (item.changed = true)}
oninput={() => (item.changed = true)}
bind:value={item.phone_number}
/></td
>
+19 -23
View File
@@ -27,11 +27,11 @@
let pager = $state({ idFrom: 0, idTo: 0 });
let items = $state([]);
let itemsLength = $derived.by(() => (items.length ? items.length : 0));
let itemsLength = $derived(items.length || 1);
let itemsBuffer = $derived(items.filter((i) => i.changed));
const functions = {
getPage: async () => {
functions.save();
async getPage(){
this.save();
if (pager.idFrom > pager.idTo) {
[pager.idFrom, pager.idTo] = [pager.idTo, pager.idFrom];
}
@@ -45,13 +45,9 @@
setTimeout(() => focusIdx(0));
},
pagerFromUpdate(){
if (itemsLength === 0) {
pager.idTo = pager.idFrom;
} else {
pager.idTo = pager.idFrom + (itemsLength - 1);
}
pager.idTo = pager.idFrom + (itemsLength - 1);
},
save: async () => {
async save(){
if (itemsBuffer.length > 0) {
const res = await fetch('/api/tickets', {
method: 'POST',
@@ -68,15 +64,15 @@
focusIdx(0);
}, 1);
},
prevPage: () => {
prevPage(){
((pager.idFrom -= itemsLength), (pager.idTo -= itemsLength));
functions.getPage();
this.getPage();
},
nextPage: () => {
nextPage(){
((pager.idFrom += itemsLength), (pager.idTo += itemsLength));
functions.getPage();
this.getPage();
},
nextLine: () => {
nextLine(){
if (items[nextIdx]) {
setTimeout(() => {
focusIdx(nextIdx);
@@ -87,7 +83,7 @@
}, 1);
}
},
prevLine: () => {
prevLine(){
if (curIdx > 0) {
setTimeout(() => {
focusIdx(prevIdx);
@@ -98,17 +94,17 @@
}, 1);
}
},
dupDown: () => {
dupDown(){
if (items[nextIdx]) {
const buffer = { ...items[curIdx] };
['prefix', 't_id'].forEach((key) => delete buffer[key]);
items[nextIdx] = { ...items[nextIdx], ...buffer, changed: true };
functions.nextLine();
this.nextLine();
} else {
focusIdx(curIdx);
}
},
dupUp: () => {
dupUp(){
if (curIdx > 0) {
const buffer = { ...items[curIdx] };
['prefix', 't_id'].forEach((key) => delete buffer[key]);
@@ -118,7 +114,7 @@
focusIdx(curIdx);
}
},
copy: () => {
copy(){
if (items[curIdx]) {
const buffer = { ...items[curIdx] };
['prefix', 't_id'].forEach((key) => delete buffer[key]);
@@ -126,7 +122,7 @@
}
setTimeout(() => focusIdx(curIdx), 1);
},
paste: () => {
paste(){
if (items[curIdx]) {
const buffer = JSON.parse(window.localStorage.getItem('tam-ticket'));
items[curIdx] = { ...items[curIdx], ...buffer, changed: true };
@@ -186,7 +182,7 @@
type="text"
class="{iS.normal} w-full"
id="{idx}_first"
onchangecapture={() => (item.changed = true)}
oninput={() => (item.changed = true)}
bind:value={item.first_name}
/></td
>
@@ -195,7 +191,7 @@
type="text"
class="{iS.normal} w-full"
id="{idx}_second"
onchangecapture={() => (item.changed = true)}
oninput={() => (item.changed = true)}
bind:value={item.last_name}
/></td
>
@@ -204,7 +200,7 @@
type="text"
class="{iS.normal} w-full"
id="{idx}_third"
onchangecapture={() => (item.changed = true)}
oninput={() => (item.changed = true)}
bind:value={item.phone_number}
/></td
>