(add): rest of search form
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
||||
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>
|
||||
@@ -73,6 +73,7 @@
|
||||
<div><h2>Admin Mode:</h2></div>
|
||||
<div class="flex-row">
|
||||
<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="/settings" target="_blank" class="styled">Settings</a>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,27 @@ export async function GET({ url }) {
|
||||
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()
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user