real init

This commit is contained in:
trev 2025-03-19 05:53:17 -04:00
parent 97402e1efc
commit 50ed3f5606
4 changed files with 39 additions and 316 deletions

View File

@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en" class="dark">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />

View File

@ -15,8 +15,11 @@
let inputElement: HTMLInputElement | null = null; let inputElement: HTMLInputElement | null = null;
let unfocusTimeout: NodeJS.Timeout; let unfocusTimeout: NodeJS.Timeout;
let charArray: string[] = [];
let lastPressed: number;
async function onKeydown(event: KeyboardEvent) { async function onKeydown(event: KeyboardEvent) {
// If we this is a manual input // If this is a manual input
if (focused) { if (focused) {
// Auto unfocus after 5 seconds of inactivity // Auto unfocus after 5 seconds of inactivity
clearTimeout(unfocusTimeout); clearTimeout(unfocusTimeout);
@ -27,24 +30,29 @@
return; return;
} }
// Check if this is an indicator that scanning has been initiated let scanning: boolean = false;
if (event.key == 'Unidentified') { if (Date.now() - lastPressed < 150) {
scanning = true; scanning = true;
return; } else {
charArray = [];
}
if (event.key.length == 1) {
charArray.push(event.key);
} }
// Send the key to onscan // Send the key to onscan
if ((event.key == 'Tab' || event.key == 'Enter' || event.key == 'Backspace' || event.key == "Home" || event.key == "Delete")) { if ((event.key == 'Tab' || event.key == 'Enter')) {
// Prevent tab or enter from doing anything // Prevent tab or enter from doing anything
event.preventDefault(); event.preventDefault();
// Send key as text // Send key as text
onscan(event.key); onscan(charArray.join(''));
} else if (event.key.length == 1) { charArray = [];
onscan(event.key); return;
} }
Haptics.impact({ style: ImpactStyle.Light }); lastPressed = Date.now();
} }
async function onKeyup(event: KeyboardEvent) { async function onKeyup(event: KeyboardEvent) {

View File

@ -20,11 +20,11 @@
<style> <style>
#header { #header {
height: var(--safe-area-inset-top); height: var(--safe-area-inset-top);
background-color: white; background-color: black;
} }
#footer { #footer {
height: var(--safe-area-inset-bottom); height: var(--safe-area-inset-bottom);
background-color: white; background-color: black;
} }
</style> </style>

View File

@ -1,316 +1,31 @@
<script lang="ts"> <script lang="ts">
import * as Dialog from '$lib/components/ui/dialog'; import Button from '$lib/components/ui/button/button.svelte';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Button, buttonVariants } from '$lib/components/ui/button';
import ScanCapture from '$lib/ScanCapture.svelte'; import ScanCapture from '$lib/ScanCapture.svelte';
import { cn } from '$lib/utils'; import { SvelteSet } from 'svelte/reactivity';
import { toast } from 'svelte-sonner'; import { fade } from 'svelte/transition';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { checkFileExists, createDirectory } from '$lib/cutils';
import { Haptics, ImpactStyle } from '@capacitor/haptics';
import { SvelteMap } from 'svelte/reactivity';
import { onMount } from 'svelte';
let showdialog = $state(false); const list: SvelteSet<string> = new SvelteSet([]);
let count = $derived(list.size);
let focused = $state(false);
let confirmation = $state(false);
const formValues: SvelteMap<string, string | undefined> = new SvelteMap();
let sessioncount = $state(0);
let daycount = $state(0);
let selected = $state('service');
function onscan(value: string) { function onscan(value: string) {
// Set as empty if undefined if (!list.has(value)) {
if (formValues.get(selected) == undefined) { list.add(value);
formValues.set(selected, '');
} }
if (value == 'Home') {
showdialog = !showdialog;
return;
}
if (value == 'Delete') {
reset();
toast.success('Cleared');
return;
}
// Advance to next input field
if (value == 'Tab' || value == 'Enter') {
if (selected == 'service') {
selected = 'serial';
} else if (selected == 'serial') {
selected = 'description';
} else if (selected == 'description') {
selected = 'manufacturer';
} else if (selected == 'manufacturer') {
selected = 'model';
} else if (selected == 'model') {
selected = 'condition';
} else if (confirmation == false) {
confirmation = true;
} else if (confirmation == true) {
save();
}
return;
}
// Cancel confirmation
if (value == 'Backspace' && confirmation == true) {
confirmation = false;
return;
}
// Go back to the previous input field
if (value == 'Backspace' && formValues.get(selected)?.length == 0) {
if (selected == 'condition') {
selected = 'model';
} else if (selected == 'model') {
selected = 'manufacturer';
} else if (selected == 'manufacturer') {
selected = 'description';
} else if (selected == 'description') {
selected = 'serial';
} else if (selected == 'serial') {
selected = 'service';
}
return;
}
// Subtract one from the current input
if (value == 'Backspace') {
formValues.set(selected, formValues.get(selected)?.slice(0, -1));
return;
}
// Append current input with new text
formValues.set(selected, formValues.get(selected) + value);
} }
async function onsubmit(event: SubmitEvent & { currentTarget: EventTarget & HTMLFormElement }) {
event.preventDefault();
Haptics.impact({ style: ImpactStyle.Medium });
confirmation = true;
}
async function save() {
Haptics.impact({ style: ImpactStyle.Medium });
// Create directory
await createDirectory({
path: 'inventory',
directory: Directory.Documents
});
const filename = `inventory/${new Date().toLocaleDateString().replaceAll('/', '-')}.csv`;
// Check if CSV file exists
const exists = await checkFileExists({
path: filename,
directory: Directory.Documents
});
// Create file if it does not exist
if (!exists) {
await Filesystem.writeFile({
path: filename,
data: '',
directory: Directory.Documents,
encoding: Encoding.UTF8
});
}
// Append data to file
await Filesystem.appendFile({
path: filename,
data: `${formValues.get('service')},${formValues.get('serial')},${formValues.get('manufacturer')},${formValues.get('model')},${formValues.get('description')},${formValues.get('condition')},${new Date().toLocaleDateString()}\n`,
directory: Directory.Documents,
encoding: Encoding.UTF8
});
sessioncount = sessioncount + 1;
daycount = daycount + 1;
toast.success('Added item to inventory');
// Reset values
reset();
}
function reset() {
confirmation = false;
selected = 'service';
formValues.set('serial', '');
formValues.set('description', '');
formValues.set('service', '');
formValues.set('manufacturer', '');
formValues.set('model', '');
formValues.set('condition', '');
}
onMount(async () => {
const filename = `inventory/${new Date().toLocaleDateString().replaceAll('/', '-')}.csv`;
// Check if CSV file exists
const exists = await checkFileExists({
path: filename,
directory: Directory.Documents
});
if (!exists) {
return;
}
// Read CSV file
const file = await Filesystem.readFile({
path: filename,
directory: Directory.Documents,
encoding: Encoding.UTF8
});
// Parse CSV file
if (typeof file.data !== 'string') {
return;
}
const lines = file.data.split("\n");
daycount = lines.length - 1;
});
</script> </script>
<ScanCapture {onscan} bind:focused /> <ScanCapture {onscan} />
<Dialog.Root bind:open={showdialog}> <div class="flex h-screen">
<Dialog.Content <div class="m-auto flex flex-col gap-8 p-4">
onEscapeKeydown={(e) => { {#key count}
e.preventDefault(); <h1
}} in:fade
> class="decoration-sky text-9xl font-bold text-center underline-offset-4"
<Dialog.Header>
<Dialog.Title>Stats</Dialog.Title>
<div class="grid grid-cols-2 gap-2 py-4">
<p>day count:</p>
<p>{daycount}</p>
<p>session count:</p>
<p>{sessioncount}</p>
</div>
<Button
type="button"
class={cn(buttonVariants({ variant: 'outline' }), 'text-black cursor-pointer')}
onclick={() => {
sessioncount = 0;
toast.success('Session count reset');
}}>Reset session count</Button
> >
</Dialog.Header> {count}
</Dialog.Content> </h1>
</Dialog.Root> {/key}
<Button class="w-28 cursor-pointer">Submit</Button>
<form class="flex flex-col gap-4 py-2 px-3 py-4" {onsubmit}>
<div class="flex gap-2 items-center">
<Label class="basis-1/4" for="service">Asset Tag</Label>
<Input
id="service"
name="service"
type="text"
disabled={confirmation}
class={cn(!focused && selected == 'service' && 'ring-offset-2 ring-2 ring-green-600')}
bind:value={() => formValues.get('service'), (v) => formValues.set('service', v)}
/>
</div> </div>
<div class="flex gap-2 items-center"> </div>
<Label class="basis-1/4" for="serial">Serial #</Label>
<Input
id="serial"
name="serial"
type="text"
disabled={confirmation}
class={cn(!focused && selected == 'serial' && 'ring-offset-2 ring-2 ring-green-600')}
bind:value={() => formValues.get('serial'), (v) => formValues.set('serial', v)}
/>
</div>
<div class="flex gap-2 items-center">
<Label class="basis-1/4" for="description">Machine Type</Label>
<Input
id="description"
name="description"
type="text"
disabled={confirmation}
class={cn(!focused && selected == 'description' && 'ring-offset-2 ring-2 ring-green-600')}
bind:value={() => formValues.get('description'), (v) => formValues.set('description', v)}
/>
</div>
<div class="flex gap-2 items-center">
<Label class="basis-1/4" for="manufacturer">Maker</Label>
<Input
id="manufacturer"
name="manufacturer"
type="text"
disabled={confirmation}
class={cn(!focused && selected == 'manufacturer' && 'ring-offset-2 ring-2 ring-green-600')}
bind:value={() => formValues.get('manufacturer'), (v) => formValues.set('manufacturer', v)}
/>
</div>
<div class="flex gap-2 items-center">
<Label class="basis-1/4" for="model">Model</Label>
<Input
id="model"
name="model"
type="text"
disabled={confirmation}
class={cn(!focused && selected == 'model' && 'ring-offset-2 ring-2 ring-green-600')}
bind:value={() => formValues.get('model'), (v) => formValues.set('model', v)}
/>
</div>
<div class="flex gap-2 items-center">
<Label class="basis-1/4" for="condition">Condition</Label>
<Input
id="condition"
name="condition"
type="text"
disabled={confirmation}
class={cn(!focused && selected == 'condition' && 'ring-offset-2 ring-2 ring-green-600')}
bind:value={() => formValues.get('condition'), (v) => formValues.set('condition', v)}
/>
</div>
{#if confirmation}
<div class="flex flex-col gap-2">
<Button
type="button"
class={cn(buttonVariants({ variant: 'outline' }), 'text-black cursor-pointer')}
onclick={() => {
reset();
toast.success('Canceled');
}}>Cancel</Button
>
<Button type="button" class="cursor-pointer" onclick={() => save()}>Confirm</Button>
</div>
{:else}
<div class="flex flex-col gap-2">
<div class="flex justify-center gap-1">
<Button
type="button"
class={cn(buttonVariants({ variant: 'outline' }), 'text-black cursor-pointer grow')}
onclick={() => {
showdialog = !showdialog;
}}>Stats</Button
>
<Button
type="button"
class={cn(buttonVariants({ variant: 'outline' }), 'text-black cursor-pointer grow')}
onclick={() => {
reset();
toast.success('Cleared');
}}>Reset</Button
>
</div>
<Button type="submit" class="cursor-pointer">Submit</Button>
</div>
{/if}
</form>