379ed232df
Replaces the Flask/Alpine web app with a SvelteKit 2 + Svelte 5 rewrite under web/, built on adapter-node and Tailwind v4. Same shape as the reference b4l budget app — no auth, stateless pass-through to InvenTree. New "scan session" flow groups mass scans into a session with live counters (scanned / succeeded / pending / failed). Unknown parts in import mode are fed to a worker pool that spawns inventree-part-import (IMPORT_CONCURRENCY, default 3, with 3-retry). Anything that can't be resolved automatically — parse errors, missing qty, invalid location, API errors, or imports that exhaust retries — drops into a Failures panel with a per-item Fix dialog (edit fields / search existing part / retry import). CSV export on the failure list. Layout is two-column on lg+: scanner + activity on the left, pending imports + failures on the right. Light-theme default. SSE on /api/events streams session and import events to the client. Barcode parser ported from the Python/JS versions and hardened for Digi-Key MH10.8.2 barcodes both with and without GS separators (old parser greedy-matched Q's digits and read "Q6" + "11ZPICK" as 611). Import worker also now treats a subprocess failure followed by a successful findPart as a success, so partial imports (part created but a duplicate parameter trips the DB constraint) no longer land in the Failures panel. Deploy artifacts: systemd unit, nginx example (SSE-friendly), and a step-by-step deploy/README. Requires inventree-part-import >= 1.9.2 on the server for InvenTree 1.x API compatibility. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
47 lines
1.1 KiB
Svelte
47 lines
1.1 KiB
Svelte
<script lang="ts">
|
|
let {
|
|
onScan,
|
|
disabled = false
|
|
}: {
|
|
onScan: (raw: string) => void;
|
|
disabled?: boolean;
|
|
} = $props();
|
|
|
|
let value = $state('');
|
|
let inputEl: HTMLInputElement | undefined = $state();
|
|
|
|
function submit() {
|
|
const v = value.trim();
|
|
if (!v) return;
|
|
onScan(v);
|
|
value = '';
|
|
inputEl?.focus();
|
|
}
|
|
|
|
export function focus() {
|
|
inputEl?.focus();
|
|
}
|
|
</script>
|
|
|
|
<section class="rounded-lg border border-slate-200 bg-white p-5 shadow-sm">
|
|
<h2 class="mb-3 text-lg font-semibold text-slate-800">Scan part</h2>
|
|
<div class="flex gap-2">
|
|
<input
|
|
bind:this={inputEl}
|
|
bind:value
|
|
onkeydown={(e) => e.key === 'Enter' && submit()}
|
|
{disabled}
|
|
placeholder="Scan barcode or type part code…"
|
|
class="flex-1 rounded-md border border-slate-300 bg-white px-4 py-3 text-lg shadow-sm focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 focus:outline-none disabled:opacity-50"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onclick={submit}
|
|
{disabled}
|
|
class="rounded-md bg-brand-500 px-5 py-3 font-medium text-white shadow-sm hover:bg-brand-600 disabled:opacity-50"
|
|
>
|
|
Process
|
|
</button>
|
|
</div>
|
|
</section>
|