diff --git a/apps/web/src/BoothScreen.tsx b/apps/web/src/BoothScreen.tsx index 7fa2a2f..e8f9314 100644 --- a/apps/web/src/BoothScreen.tsx +++ b/apps/web/src/BoothScreen.tsx @@ -5,6 +5,7 @@ import { fetchEvents, fetchOccupancy, type LedgerEvent, type Occupancy } from ". import { qk } from "./lib/query.js"; import { useLiveStore } from "./lib/live-store.js"; import { useShift } from "./lib/use-shift.js"; +import { useScanner } from "./lib/use-scanner.js"; import { Panel } from "./ui/Panel.js"; import { StatusDot } from "./ui/StatusDot.js"; import { BoothPayModal } from "./BoothPayModal.js"; @@ -132,6 +133,11 @@ export function BoothScreen() { // The ledger event open in the read-only detail modal (null = closed). const [detailEvent, setDetailEvent] = useState(null); + // A hardware scan opens the pay/exit modal regardless of focus (the operator needn't + // click the ticket field first). Paused while a modal is already up — a scan must not + // abandon an in-progress payment (the operator finishes/closes, then scans the next). + useScanner({ onScan: setActiveTicket, paused: activeTicket != null || detailEvent != null }); + // Live-feed filters: free-text search, event category, and direction/source. const [feedSearch, setFeedSearch] = useState(""); const [feedType, setFeedType] = useState(""); diff --git a/apps/web/src/lib/use-scanner.ts b/apps/web/src/lib/use-scanner.ts new file mode 100644 index 0000000..0c931b9 --- /dev/null +++ b/apps/web/src/lib/use-scanner.ts @@ -0,0 +1,78 @@ +import { useEffect, useRef } from "react"; + +// Global hardware-scanner capture (HID "keyboard wedge"). A barcode/QR scanner types +// the code as a fast keystroke burst followed by Enter — like a keyboard, but far +// faster than a human. This hook listens at the DOCUMENT level so a scan fires the +// callback no matter what's focused (or if nothing is), unlike a single that +// only catches scans while it holds focus. See wiki/concepts/booth-console.md. +// +// It does NOT hijack manual typing: keystrokes into an /