Files
MABEA/frontend/src/pages/AktePage.tsx
T
patrickandClaude Sonnet 5 30e2db63ae
CI / backend-tests (push) Successful in 2m1s
CI / frontend-build (push) Successful in 18s
feat(ui): UI-007 Aktionsleiste auf der Objektakte
Kontrolle starten/Mangel melden direkt unter Status statt nur ein
Button am Seitenende - nach dem QR-Scan (MOBILE-002 landet auf der
Akte) sofort erreichbar. Mangel melden springt mit ?objekt_id=X in
MangelListePage und wählt das Objekt direkt vor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
2026-09-06 21:41:36 +02:00

164 lines
6.3 KiB
TypeScript

import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import { apiRequest } from "../api/client";
import type { Akte } from "../api/types";
import { DokumentePanel } from "../components/DokumentePanel";
import { ReadinessBadge } from "../components/status/ReadinessBadge";
import { GRUND_TEXT } from "../components/status/gruendeText";
const MANGEL_PRIORITAET_LABEL: Record<string, string> = {
niedrig: "Niedrig",
normal: "Normal",
hoch: "Hoch",
};
// FILE-006: EIN Screen für alle Teilbereiche einer Ressource statt mehrerer
// Einzelseiten - konsumiert den FILE-001-Aggregations-Endpunkt.
export function AktePage() {
const { objektId } = useParams<{ objektId: string }>();
const [akte, setAkte] = useState<Akte | null>(null);
const [fehler, setFehler] = useState<string | null>(null);
const [laedt, setLaedt] = useState(true);
useEffect(() => {
if (!objektId) return;
setLaedt(true);
apiRequest<Akte>(`/akte/objekt/${objektId}`)
.then(setAkte)
.catch(() => setFehler("Akte konnte nicht geladen werden."))
.finally(() => setLaedt(false));
}, [objektId]);
if (laedt) return <main className="page"><p className="text-muted">Lade</p></main>;
if (fehler) return <main className="page"><div className="alert alert-danger">{fehler}</div></main>;
if (!akte) return null;
const { objekt } = akte;
return (
<main className="page">
<div className="row-between" style={{ alignItems: "flex-start" }}>
<div>
<h1 style={{ marginBottom: "0.15rem" }}>{objekt.name}</h1>
<div className="text-muted">
{objekt.code} · {akte.objekttyp_name} · Objekt-Status: {objekt.status}
</div>
</div>
<ReadinessBadge status={akte.einsatzbereitschaft_status} />
</div>
{akte.einsatzbereitschaft_gruende.length > 0 && (
<ul className="card-list" style={{ marginTop: "0.5rem" }}>
{akte.einsatzbereitschaft_gruende.map((g) => (
<li key={g} className="text-muted" style={{ fontSize: "0.9rem" }}>
{GRUND_TEXT[g] ?? g}
</li>
))}
</ul>
)}
{/* UI-007 (Epic 20): nach dem QR-Scan (MOBILE-002) landet man hier - die
Kernaktionen sollen direkt sichtbar sein, nicht erst am Seitenende. */}
<div className="row" style={{ flexWrap: "wrap", marginTop: "1rem" }}>
<Link to={`/objekte/${objekt.id}/kontrolle`} className="btn btn-primary">
Kontrolle starten
</Link>
<Link to={`/maengel?objekt_id=${objekt.id}`} className="btn btn-secondary">
Mangel melden
</Link>
</div>
<div className="card" style={{ marginTop: "1rem" }}>
<h3 style={{ marginTop: 0 }}>Stammdaten</h3>
<div className="stack">
<div>Standort: {akte.standort_name}</div>
<div>
Letzte Kontrolle:{" "}
{akte.letzte_kontrolle_am
? new Date(akte.letzte_kontrolle_am).toLocaleString("de-DE")
: "noch nie kontrolliert"}
</div>
{akte.fahrzeugdetails && (
<>
{akte.fahrzeugdetails.kennzeichen && <div>Kennzeichen: {akte.fahrzeugdetails.kennzeichen}</div>}
{akte.fahrzeugdetails.funkrufname && <div>Funkrufname: {akte.fahrzeugdetails.funkrufname}</div>}
{akte.fahrzeugdetails.funkkenner && <div>Funkkenner: {akte.fahrzeugdetails.funkkenner}</div>}
{akte.fahrzeugdetails.kilometerstand !== null && (
<div>Kilometerstand: {akte.fahrzeugdetails.kilometerstand} km</div>
)}
</>
)}
</div>
</div>
<div className="card" style={{ marginTop: "1rem" }}>
<h3 style={{ marginTop: 0 }}>Verantwortlichkeit</h3>
{akte.zustaendige_benutzer.length === 0 && akte.kontrollverantwortung.length === 0 && (
<p className="text-muted">Keine Zuordnung hinterlegt.</p>
)}
{akte.zustaendige_benutzer.length > 0 && (
<ul>
{akte.zustaendige_benutzer.map((z) => (
<li key={z.id}>
{z.name} <span className="text-muted">({z.quelle === "standort" ? "über Standort" : "direkt"})</span>
</li>
))}
</ul>
)}
</div>
<div className="card" style={{ marginTop: "1rem" }}>
<h3 style={{ marginTop: 0 }}>Prüfungen (Geräte)</h3>
{akte.geraete.length === 0 && <p className="text-muted">Keine Geräte-Instanzen erfasst.</p>}
{akte.geraete.length > 0 && (
<ul>
{akte.geraete.map((g) => (
<li key={g.id}>
SN {g.seriennummer} {" "}
<span className={g.status === "einsatzbereit" ? "text-success" : "text-danger"}>{g.status}</span>
{g.naechste_pruefung && (
<span className="text-muted"> · nächste Prüfung {new Date(g.naechste_pruefung).toLocaleDateString("de-DE")}</span>
)}
</li>
))}
</ul>
)}
</div>
<div className="card" style={{ marginTop: "1rem" }}>
<h3 style={{ marginTop: 0 }}>Mängel</h3>
{akte.maengel.length === 0 && <p className="text-muted">Keine Mängel gemeldet.</p>}
{akte.maengel.length > 0 && (
<ul>
{akte.maengel.map((m) => (
<li key={m.id}>
{m.beschreibung} {MANGEL_PRIORITAET_LABEL[m.prioritaet]}, {m.status}
</li>
))}
</ul>
)}
</div>
<div className="card" style={{ marginTop: "1rem" }}>
<h3 style={{ marginTop: 0 }}>Dokumente</h3>
<DokumentePanel entitaetTyp="objekt" entitaetId={String(objekt.id)} onFehler={setFehler} />
</div>
<div className="card" style={{ marginTop: "1rem" }}>
<h3 style={{ marginTop: 0 }}>Historie</h3>
{akte.historie.length === 0 && <p className="text-muted">Noch keine Änderungen protokolliert.</p>}
{akte.historie.length > 0 && (
<ul>
{akte.historie.map((h) => (
<li key={h.id}>
{new Date(h.zeitpunkt).toLocaleString("de-DE")} {h.ereignistyp}
{h.benutzer_name && <span className="text-muted"> ({h.benutzer_name})</span>}
</li>
))}
</ul>
)}
</div>
</main>
);
}