fix(ui): Prüftermine/Ablaufdaten verlinken, Prüfung direkt aus der Akte erfassbar
CI / backend-tests (push) Failing after 2m0s
CI / frontend-build (push) Successful in 17s

Dashboard-Listen "Nächste Prüftermine"/"Bevorstehende Ablaufdaten"
hatten keinen Link zum betroffenen Objekt - ein überfälliger Termin
war nicht erreichbar. Zusätzlich: "Prüfung heute durchgeführt"-Button
je Gerät auf der Akte (PATCH /geraete/{id}), bisher nur über die
Admin-Objektbearbeitung möglich.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-06 22:50:42 +02:00
co-authored by Claude Sonnet 5
parent 9721873088
commit d607a1101d
2 changed files with 34 additions and 9 deletions
+30 -7
View File
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { apiRequest } from "../api/client"; import { apiRequest } from "../api/client";
import { useAuth } from "../auth/AuthContext";
import type { Akte } from "../api/types"; import type { Akte } from "../api/types";
import { DokumentePanel } from "../components/DokumentePanel"; import { DokumentePanel } from "../components/DokumentePanel";
import { ReadinessBadge } from "../components/status/ReadinessBadge"; import { ReadinessBadge } from "../components/status/ReadinessBadge";
@@ -18,18 +19,33 @@ const MANGEL_PRIORITAET_LABEL: Record<string, string> = {
// Einzelseiten - konsumiert den FILE-001-Aggregations-Endpunkt. // Einzelseiten - konsumiert den FILE-001-Aggregations-Endpunkt.
export function AktePage() { export function AktePage() {
const { objektId } = useParams<{ objektId: string }>(); const { objektId } = useParams<{ objektId: string }>();
const { istVerantwortlich } = useAuth();
const [akte, setAkte] = useState<Akte | null>(null); const [akte, setAkte] = useState<Akte | null>(null);
const [fehler, setFehler] = useState<string | null>(null); const [fehler, setFehler] = useState<string | null>(null);
const [laedt, setLaedt] = useState(true); const [laedt, setLaedt] = useState(true);
useEffect(() => { function laden() {
if (!objektId) return; if (!objektId) return;
setLaedt(true); setLaedt(true);
apiRequest<Akte>(`/akte/objekt/${objektId}`) apiRequest<Akte>(`/akte/objekt/${objektId}`)
.then(setAkte) .then(setAkte)
.catch(() => setFehler("Akte konnte nicht geladen werden.")) .catch(() => setFehler("Akte konnte nicht geladen werden."))
.finally(() => setLaedt(false)); .finally(() => setLaedt(false));
}, [objektId]); }
useEffect(laden, [objektId]);
async function pruefungDurchgefuehrt(geraetId: string) {
try {
await apiRequest(`/geraete/${geraetId}`, {
method: "PATCH",
body: { status: "einsatzbereit", pruefdatum: new Date().toISOString().slice(0, 10) },
});
laden();
} catch {
setFehler("Prüfung konnte nicht erfasst werden.");
}
}
if (laedt) return <main className="page"><p className="text-muted">Lade</p></main>; 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 (fehler) return <main className="page"><div className="alert alert-danger">{fehler}</div></main>;
@@ -114,11 +130,18 @@ export function AktePage() {
{akte.geraete.length > 0 && ( {akte.geraete.length > 0 && (
<ul> <ul>
{akte.geraete.map((g) => ( {akte.geraete.map((g) => (
<li key={g.id}> <li key={g.id} className="row-between" style={{ padding: "0.25rem 0" }}>
SN {g.seriennummer} {" "} <span>
<span className={g.status === "einsatzbereit" ? "text-success" : "text-danger"}>{g.status}</span> SN {g.seriennummer} {" "}
{g.naechste_pruefung && ( <span className={g.status === "einsatzbereit" ? "text-success" : "text-danger"}>{g.status}</span>
<span className="text-muted"> · nächste Prüfung {new Date(g.naechste_pruefung).toLocaleDateString("de-DE")}</span> {g.naechste_pruefung && (
<span className="text-muted"> · nächste Prüfung {new Date(g.naechste_pruefung).toLocaleDateString("de-DE")}</span>
)}
</span>
{istVerantwortlich && (
<button className="btn btn-secondary" onClick={() => pruefungDurchgefuehrt(g.id)}>
Prüfung heute durchgeführt
</button>
)} )}
</li> </li>
))} ))}
+4 -2
View File
@@ -218,7 +218,8 @@ export function DashboardPage() {
{prueftermine.map((p, i) => ( {prueftermine.map((p, i) => (
<li key={i} className="row-between" style={{ padding: "0.3rem 0" }}> <li key={i} className="row-between" style={{ padding: "0.3rem 0" }}>
<span> <span>
{p.bezeichnung} <span className="text-muted">({p.faelligkeitsdatum})</span> <Link to={`/akte/objekt/${p.objekt_id}`}>{p.bezeichnung}</Link>{" "}
<span className="text-muted">({p.faelligkeitsdatum})</span>
</span> </span>
<InspectionStatus status={p.status} tage={p.verbleibende_tage} /> <InspectionStatus status={p.status} tage={p.verbleibende_tage} />
</li> </li>
@@ -236,7 +237,8 @@ export function DashboardPage() {
{ablaufdaten.map((a) => ( {ablaufdaten.map((a) => (
<li key={`${a.objekt_id}-${a.material_id}`} className="row-between" style={{ padding: "0.3rem 0" }}> <li key={`${a.objekt_id}-${a.material_id}`} className="row-between" style={{ padding: "0.3rem 0" }}>
<span> <span>
Objekt #{a.objekt_id} Material #{a.material_id} <span className="text-muted">({a.ablaufdatum})</span> <Link to={`/akte/objekt/${a.objekt_id}`}>Objekt #{a.objekt_id}</Link> Material #{a.material_id}{" "}
<span className="text-muted">({a.ablaufdatum})</span>
</span> </span>
<InventoryStatus status={a.status} tage={a.verbleibende_tage} /> <InventoryStatus status={a.status} tage={a.verbleibende_tage} />
</li> </li>