"use client"; import * as React from "react"; import { useRouter } from "next/navigation"; import { BrowserQRCodeReader } from "@zxing/browser"; import type { Result } from "@zxing/library"; import { Camera, CameraOff, KeyboardIcon, RefreshCw } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Fehlermeldung } from "@/components/stammdaten/anzeige"; import { inventarnummerAusScan } from "@/lib/stammdaten/qr-parser"; /** * QR-Scanner über die Kamera. * * Bewusst mit @zxing/browser statt der eingebauten BarcodeDetector-API: die * gibt es in Firefox und in Safari auf dem iPad nicht, und im Gerätehaus steht * genau so ein Tablet. * * Zwei Dinge, die in der Praxis zählen: * - Die Kamera wird beim Verlassen der Seite wirklich freigegeben. Sonst * bleibt die Leuchte an und der nächste Aufruf bekommt kein Bild. * - Es gibt immer einen Weg ohne Kamera. Wenn die Freigabe verweigert wurde * oder das Etikett zerkratzt ist, tippt man die Nummer ein. */ export function Scanner() { const router = useRouter(); const videoRef = React.useRef(null); const steuerungRef = React.useRef<{ stop: () => void } | null>(null); const [laeuft, setLaeuft] = React.useState(false); const [fehler, setFehler] = React.useState(null); const [letzterScan, setLetzterScan] = React.useState(null); const [eingabe, setEingabe] = React.useState(""); const stoppen = React.useCallback(() => { steuerungRef.current?.stop(); steuerungRef.current = null; setLaeuft(false); }, []); // Kamera in jedem Fall freigeben, auch bei einem Wechsel per Zurück-Taste. React.useEffect(() => stoppen, [stoppen]); const oeffnen = React.useCallback( (inventarnummer: string) => { stoppen(); router.push(`/g/${encodeURIComponent(inventarnummer)}`); }, [router, stoppen], ); async function starten() { setFehler(null); setLetzterScan(null); if (!videoRef.current) return; if (!navigator.mediaDevices?.getUserMedia) { setFehler( "Dieser Browser gibt keinen Kamerazugriff frei. Bitte die Inventarnummer eintippen.", ); return; } try { const leser = new BrowserQRCodeReader(); const steuerung = await leser.decodeFromVideoDevice( undefined, videoRef.current, (ergebnis: Result | undefined) => { if (!ergebnis) return; const roh = ergebnis.getText(); const nummer = inventarnummerAusScan(roh); if (!nummer) { // Fremde Codes kommen ständig vor — der Hersteller-Barcode klebt // oft direkt daneben. Kein Abbruch, nur ein Hinweis. setLetzterScan(roh); return; } oeffnen(nummer); }, ); steuerungRef.current = steuerung; setLaeuft(true); } catch (ausnahme) { const name = (ausnahme as { name?: string })?.name; if (name === "NotAllowedError") { setFehler( "Der Kamerazugriff wurde abgelehnt. Er lässt sich in den Browsereinstellungen wieder freigeben — oder Sie tippen die Inventarnummer ein.", ); } else if (name === "NotFoundError") { setFehler("An diesem Gerät wurde keine Kamera gefunden."); } else { setFehler(`Die Kamera ließ sich nicht öffnen: ${String(ausnahme)}`); } } } function eingabeAbsenden(ereignis: React.FormEvent) { ereignis.preventDefault(); const nummer = inventarnummerAusScan(eingabe); if (!nummer) { setFehler("Das sieht nicht nach einer Inventarnummer aus."); return; } oeffnen(nummer); } return (
{fehler && }
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
{laeuft ? ( ) : ( )} {laeuft && ( )}
{letzterScan && (

Erkannt, aber kein FLORIAN-Etikett:{" "} {letzterScan}
Vermutlich der Barcode des Herstellers. Bitte den Aufkleber mit dem FLORIAN-Code scannen.

)}
setEingabe(ereignis.target.value)} placeholder="INV-0042" autoComplete="off" />

Funktioniert auch ohne Kamera — praktisch, wenn das Etikett unleserlich geworden ist.

); }