fix(ui): Prüftermine/Ablaufdaten verlinken, Prüfung direkt aus der Akte erfassbar
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:
@@ -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>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user