Files
MABEA/frontend/src/components/ZaehlerstandPanel.tsx
T
patrickandClaude Sonnet 5 69f66c6d1c
CI / backend-tests (push) Successful in 3m25s
CI / frontend-build (push) Successful in 21s
feat(assets): ASSET-009 generischer Nutzungszähler (km/Betriebsstunden)
Fahrzeugdetails war nie technisch auf Fahrzeuge beschränkt, aber es gab
keinen Weg, den Zählerstand überhaupt zu setzen (nur Formular-Ersatz-PUT
ohne Validierung, keine Frontend-Anbindung). Neuer PATCH-Endpunkt mit
Monoton-Validierung (Rückwärtslauf nur Administrator), neues
ZaehlerstandPanel in der Akte - bewusst ohne Fahrzeug-Gate, damit
Stromerzeuger/Pumpen denselben Mechanismus nutzen können.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDMvneg6wKfXdXvTYNd3dN
2026-09-12 23:35:57 +02:00

87 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { apiRequest, ApiError } from "../api/client";
import type { Fahrzeugdetails } from "../api/types";
interface Props {
objektId: number;
fahrzeugdetails: Fahrzeugdetails | null;
onAktualisiert: () => Promise<void>;
}
// ASSET-009: generischer Nutzungszähler - bewusst NICHT auf Fahrzeuge
// beschränkt (siehe Kachel-Nachtrag), deshalb hier ohne istFahrzeug-Prüfung
// immer angezeigt. Kilometerstand bleibt bei einem Nicht-Fahrzeug (z.B.
// Stromerzeuger) einfach ungenutzt/leer.
export function ZaehlerstandPanel({ objektId, fahrzeugdetails, onAktualisiert }: Props) {
const [kilometerstand, setKilometerstand] = useState("");
const [betriebsstunden, setBetriebsstunden] = useState("");
const [wirdGespeichert, setWirdGespeichert] = useState(false);
const [fehler, setFehler] = useState<string | null>(null);
async function melden() {
if (!kilometerstand && !betriebsstunden) return;
setWirdGespeichert(true);
setFehler(null);
try {
await apiRequest(`/objekte/${objektId}/zaehlerstand`, {
method: "PATCH",
body: {
kilometerstand: kilometerstand ? Number(kilometerstand) : null,
betriebsstunden: betriebsstunden || null,
},
});
setKilometerstand("");
setBetriebsstunden("");
await onAktualisiert();
} catch (e) {
if (e instanceof ApiError && e.status === 409) {
setFehler("Wert liegt unter dem bisherigen Zählerstand - nur Administratoren dürfen zurücksetzen.");
} else {
setFehler("Zählerstand konnte nicht gespeichert werden.");
}
} finally {
setWirdGespeichert(false);
}
}
return (
<div className="card">
<h3 style={{ marginTop: 0 }}>Zählerstand</h3>
<div style={{ marginBottom: "0.5rem" }}>
<span className="text-muted">Kilometerstand: </span>
{fahrzeugdetails?.kilometerstand ?? "–"} km
<span className="text-muted" style={{ marginLeft: "1rem" }}>
Betriebsstunden:{" "}
</span>
{fahrzeugdetails?.betriebsstunden ?? "–"} h
</div>
<div className="row" style={{ flexWrap: "wrap", gap: "0.5rem" }}>
<input
className="input"
type="number"
placeholder="Neuer Kilometerstand"
value={kilometerstand}
onChange={(e) => setKilometerstand(e.target.value)}
/>
<input
className="input"
type="number"
step="0.1"
placeholder="Neue Betriebsstunden"
value={betriebsstunden}
onChange={(e) => setBetriebsstunden(e.target.value)}
/>
<button
className="btn btn-primary"
onClick={melden}
disabled={wirdGespeichert || (!kilometerstand && !betriebsstunden)}
>
Melden
</button>
</div>
{fehler && <p style={{ color: "var(--color-danger)", marginBottom: 0 }}>{fehler}</p>}
</div>
);
}