Sprint 8: modernes Design-System für die React-PWA
CI / backend-tests (push) Successful in 54s
CI / frontend-build (push) Successful in 1m43s

Gemeinsames Stylesheet (Tokens, Karten, Buttons, Badges, Formulare) plus
AppShell (Kopfzeile mit Navigation/Logout) statt Inline-Styles je Screen.
Kein UI-Framework als neue Abhängigkeit - schlankes eigenes CSS, damit
Build/Deploy-Weg unverändert bleibt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
This commit is contained in:
2026-09-04 00:38:02 +02:00
co-authored by Claude Sonnet 5
parent a97886defd
commit e3ed5e0f28
8 changed files with 598 additions and 162 deletions
+21 -26
View File
@@ -3,7 +3,6 @@ import { Link } from "react-router-dom";
import { apiRequest } from "../api/client";
import type { Objekt } from "../api/types";
import { useAuth } from "../auth/AuthContext";
// 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,
@@ -12,12 +11,13 @@ export function ObjektListPage() {
const [objekte, setObjekte] = useState<Objekt[]>([]);
const [suche, setSuche] = useState("");
const [ladefehler, setLadefehler] = useState<string | null>(null);
const { istAdmin } = useAuth();
const [laedt, setLaedt] = useState(true);
useEffect(() => {
apiRequest<Objekt[]>("/objekte")
.then(setObjekte)
.catch(() => setLadefehler("Objektliste konnte nicht geladen werden."));
.catch(() => setLadefehler("Objektliste konnte nicht geladen werden."))
.finally(() => setLaedt(false));
}, []);
const gefiltert = objekte.filter(
@@ -27,36 +27,31 @@ export function ObjektListPage() {
);
return (
<main style={{ maxWidth: 480, margin: "0 auto", padding: "1rem" }}>
<main className="page">
<h1>Objekte</h1>
{istAdmin && (
<Link to="/admin" style={{ display: "inline-block", marginBottom: "1rem" }}>
Administration
</Link>
)}
<input
className="input input-search"
placeholder="Suche oder Code scannen…"
value={suche}
onChange={(e) => setSuche(e.target.value)}
style={{ width: "100%", fontSize: "1.2rem", padding: "0.75rem", marginBottom: "1rem" }}
style={{ marginBottom: "1.25rem" }}
/>
{ladefehler && <p style={{ color: "crimson" }}>{ladefehler}</p>}
<ul style={{ listStyle: "none", padding: 0 }}>
{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} style={{ marginBottom: "0.5rem" }}>
<Link
to={`/objekte/${objekt.id}/kontrolle`}
style={{
display: "block",
padding: "1rem",
border: "1px solid #ccc",
borderRadius: 8,
textDecoration: "none",
color: "inherit",
}}
>
<strong>{objekt.name}</strong>
<div style={{ fontSize: "0.9rem", color: "#666" }}>{objekt.code}</div>
<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>
</Link>
</li>
))}