new updates

This commit is contained in:
2026-09-17 14:22:45 +05:30
parent eb36e0acfa
commit c2b92f3086
261 changed files with 102090 additions and 545 deletions
@@ -0,0 +1,82 @@
"use client"
// Reusable capture box for USB/Bluetooth barcode & RFID scanners. Those devices are plain HID
// keyboards: they "type" the scanned code into whatever input is focused, then send Enter — so
// there is no camera/getUserMedia code here, just a text input that reacts to Enter (and to
// losing focus with content still in it, in case a scanner is configured without a trailing
// Enter) and clears itself after each read. It stays auto-focused so the next scan always lands
// here rather than wherever the cursor last was in a large form.
import { useEffect, useRef, useState } from "react"
import { ScanBarcode } from "lucide-react"
import { cn } from "@/lib/utils"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export interface ScanInputProps {
/** Fired once per scan with the trimmed code. The input is already cleared by the time this runs. */
onScan: (code: string) => void
label?: string
/** Helper text under the input, e.g. "Scan item or lot barcode". */
hint?: string
placeholder?: string
disabled?: boolean
className?: string
}
export function ScanInput({
onScan,
label = "Scan",
hint = "Scan item or lot barcode",
placeholder = "Scan or type a barcode, then press Enter",
disabled = false,
className,
}: ScanInputProps) {
const [value, setValue] = useState("")
const inputRef = useRef<HTMLInputElement>(null)
// Refocus whenever the field becomes usable again (e.g. after being briefly disabled while a
// lookup for the previous scan was in flight) so a scanner's next read always lands here.
useEffect(() => {
if (!disabled) inputRef.current?.focus()
}, [disabled])
function commit() {
const code = value.trim()
setValue("")
if (!code) return
onScan(code)
}
return (
<div className={cn("flex flex-col gap-2", className)}>
<Label className="flex items-center gap-1.5 text-base">
<ScanBarcode className="size-4 text-muted-foreground" />
{label}
</Label>
<Input
ref={inputRef}
value={value}
disabled={disabled}
placeholder={placeholder}
autoComplete="off"
onChange={(e) => setValue(e.target.value)}
onKeyDown={(e) => {
if (e.key !== "Enter") return
// A scanner's Enter is just another keystroke to the browser — stop it from also
// submitting an enclosing <form> or activating a nearby default button.
e.preventDefault()
commit()
}}
onBlur={() => {
// Guards a scanner configured without a trailing Enter — but only fires for content
// that's actually there, so a plain click-away with an empty box does nothing.
if (value.trim()) commit()
}}
className="h-11 text-base"
/>
{hint && <p className="text-sm text-muted-foreground">{hint}</p>}
</div>
)
}