Karte 10: Kamera-Scan im Frontend (Objekt-Code, Scan-First-Einstieg)
BarcodeScanner-Komponente auf @zxing/browser (Code128, iOS/Safari-kompatibel im Gegensatz zur nativen BarcodeDetector-API). Treffer springt direkt ins Kontroll-Screen, sonst Fallback ins bestehende Such-Textfeld. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -1,17 +1,20 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
|
||||
import { apiRequest } from "../api/client";
|
||||
import { apiRequest, ApiError } from "../api/client";
|
||||
import type { Objekt } from "../api/types";
|
||||
import { BarcodeScanner } from "../components/BarcodeScanner";
|
||||
|
||||
// Prompt 11 Screen 1: Standort/Objekt wählen. QR-/Barcode-Scan (Karte 10) ist
|
||||
// Roadmap - dieser Screen bietet bewusst schon die Code-Suche als Textfeld an,
|
||||
// ein späterer Kamera-Scan kann denselben Code einfach in dasselbe Feld schreiben.
|
||||
// Prompt 11 Screen 1: Standort/Objekt wählen. Objekt-Code (Karte 10) →
|
||||
// Direktsprung ins Kontroll-Screen, sonst füllt der Scan dasselbe Textfeld wie
|
||||
// die manuelle Suche (Kamera fällt bei Etikettschaden auf Tippen zurück).
|
||||
export function ObjektListPage() {
|
||||
const navigate = useNavigate();
|
||||
const [objekte, setObjekte] = useState<Objekt[]>([]);
|
||||
const [suche, setSuche] = useState("");
|
||||
const [ladefehler, setLadefehler] = useState<string | null>(null);
|
||||
const [laedt, setLaedt] = useState(true);
|
||||
const [scannerOffen, setScannerOffen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
apiRequest<Objekt[]>("/objekte")
|
||||
@@ -20,6 +23,20 @@ export function ObjektListPage() {
|
||||
.finally(() => setLaedt(false));
|
||||
}, []);
|
||||
|
||||
async function scanVerarbeiten(code: string) {
|
||||
setScannerOffen(false);
|
||||
try {
|
||||
const objekt = await apiRequest<Objekt>(`/objekte/code/${encodeURIComponent(code)}`);
|
||||
navigate(`/objekte/${objekt.id}/kontrolle`);
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.status === 404) {
|
||||
setSuche(code);
|
||||
} else {
|
||||
setLadefehler("Scan konnte nicht verarbeitet werden.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const gefiltert = objekte.filter(
|
||||
(o) =>
|
||||
o.name.toLowerCase().includes(suche.toLowerCase()) ||
|
||||
@@ -29,13 +46,20 @@ export function ObjektListPage() {
|
||||
return (
|
||||
<main className="page">
|
||||
<h1>Objekte</h1>
|
||||
<input
|
||||
className="input input-search"
|
||||
placeholder="Suche oder Code scannen…"
|
||||
value={suche}
|
||||
onChange={(e) => setSuche(e.target.value)}
|
||||
style={{ marginBottom: "1.25rem" }}
|
||||
/>
|
||||
<div className="row" style={{ marginBottom: "1.25rem" }}>
|
||||
<input
|
||||
className="input input-search"
|
||||
placeholder="Suche oder Code…"
|
||||
value={suche}
|
||||
onChange={(e) => setSuche(e.target.value)}
|
||||
/>
|
||||
<button className="btn btn-secondary" onClick={() => setScannerOffen(true)}>
|
||||
Scannen
|
||||
</button>
|
||||
</div>
|
||||
{scannerOffen && (
|
||||
<BarcodeScanner onErkannt={scanVerarbeiten} onSchliessen={() => setScannerOffen(false)} />
|
||||
)}
|
||||
{ladefehler && <div className="alert alert-danger">{ladefehler}</div>}
|
||||
{laedt && <p className="text-muted">Lade…</p>}
|
||||
{!laedt && !ladefehler && gefiltert.length === 0 && (
|
||||
|
||||
Reference in New Issue
Block a user