(add): rest of search form

This commit is contained in:
2026-01-14 21:41:07 -05:00
parent 9067108798
commit 767eb8a339
5 changed files with 435 additions and 2 deletions

View File

@@ -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">

View 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>

View File

@@ -17,7 +17,7 @@
current_prefix = {...new_prefix}; current_prefix = {...new_prefix};
} }
}) })
if (browser) { if (browser) {
document.title = `${venue} - Main Menu`; document.title = `${venue} - Main Menu`;
hotkeys.filter = function(event) {return true}; hotkeys.filter = function(event) {return true};
@@ -73,6 +73,7 @@
<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="/search/tickets" target="_blank" class="styled">Search Tickets</a>
<a href="/backuprestore" target="_blank" class="styled">Backup/Restore</a> <a href="/backuprestore" target="_blank" class="styled">Backup/Restore</a>
<a href="/settings" target="_blank" class="styled">Settings</a> <a href="/settings" target="_blank" class="styled">Settings</a>
</div> </div>

View File

@@ -10,6 +10,27 @@ export async function GET({ url }) {
sPhoneNumber = url.searchParams.get("phone_number") || ""; sPhoneNumber = url.searchParams.get("phone_number") || "";
if (env.TAM3_REMOTE) { 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 { } else {
const results = await db const results = await db
.select() .select()

View 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>