Globale Suche/Quick Actions per Strg/Cmd+K (CommandPalette): Objekt- Suche nach Name/Code, Sprung zu QR-Scan/Material/Lager/Personal/ Verwaltung. AdminPage-Tab jetzt im URL-Query (?tab=...) statt nur lokalem State, damit die Palette gezielt verlinken kann, ohne Routen zu duplizieren. Bewusst keine neuen Sidebar-Punkte fuer Fahrzeuge/ Kontrollen/Pruefungen - dafuer gibt es keine eigenstaendigen Listen- Seiten in MABEA. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
111 lines
4.0 KiB
TypeScript
111 lines
4.0 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
|
|
|
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. Objekt-Code (MOBILE-002) →
|
|
// Direktsprung zur Akte (Zielseite laut Kachel 15), 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);
|
|
const [suchparameter, setSuchparameter] = useSearchParams();
|
|
|
|
// UI-002 (Epic 20): Quick Action "QR scannen" aus der Command Palette springt
|
|
// hierher mit ?scan=1 und öffnet den Scanner direkt, statt nur die Liste zu zeigen.
|
|
useEffect(() => {
|
|
if (suchparameter.get("scan") === "1") {
|
|
setScannerOffen(true);
|
|
setSuchparameter({}, { replace: true });
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
apiRequest<Objekt[]>("/objekte")
|
|
.then(setObjekte)
|
|
.catch(() => setLadefehler("Objektliste konnte nicht geladen werden."))
|
|
.finally(() => setLaedt(false));
|
|
}, []);
|
|
|
|
async function scanVerarbeiten(code: string) {
|
|
setScannerOffen(false);
|
|
try {
|
|
const objekt = await apiRequest<Objekt>(`/objekte/code/${encodeURIComponent(code)}`);
|
|
navigate(`/akte/objekt/${objekt.id}`);
|
|
} 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()) ||
|
|
o.code.toLowerCase().includes(suche.toLowerCase())
|
|
);
|
|
|
|
return (
|
|
<main className="page">
|
|
<h1>Objekte</h1>
|
|
<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 && (
|
|
<p className="text-muted">
|
|
{objekte.length === 0 ? "Noch keine Objekte angelegt." : "Keine Treffer."}
|
|
</p>
|
|
)}
|
|
<ul className="card-list">
|
|
{gefiltert.map((objekt) => (
|
|
<li key={objekt.id}>
|
|
<Link to={`/objekte/${objekt.id}/kontrolle`} className="card card-link">
|
|
<div className="card-title-row">
|
|
<strong>{objekt.name}</strong>
|
|
<span className="badge badge-neutral">{objekt.status}</span>
|
|
</div>
|
|
<div className="card-subtitle">{objekt.code}</div>
|
|
<div className="text-muted" style={{ fontSize: "0.85rem" }}>
|
|
{objekt.letzte_kontrolle_am
|
|
? `Letzte Kontrolle: ${new Date(objekt.letzte_kontrolle_am).toLocaleDateString("de-DE")}`
|
|
: "Noch nie kontrolliert"}
|
|
</div>
|
|
</Link>
|
|
<Link
|
|
to={`/akte/objekt/${objekt.id}`}
|
|
className="text-muted"
|
|
style={{ fontSize: "0.85rem" }}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
Akte ansehen
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</main>
|
|
);
|
|
}
|