Files
audi-app/companion-app/src/screens/Reifen.tsx
T
Paul Nothaft 8e6e5bc2ee Phase 7c: restliche Screens - alle 21 Seiten stehen
Fahrzeugdaten, Batterieverlauf, Service mit Werkstatt und Servicebuch,
Reifen, die ganze Versicherungs- und Steuergruppe, Einstellungen in voller
Tiefe und die Live-Ansicht.

Portiert dazu: die eigene Oelwechsel-Prognose (rechnet ab dem letzten
Wechsel im Servicebuch neu, weil der Langzeitschnitt dafuer zu traege ist),
Kalenderdateien fuer Termine und die CSV-Ausgabe im deutschen Format.

Die Live-Ansicht ist gebaut, aber ueber einen Schalter abgeschaltet: die
Werte dafuer liefert erst der FMM003. Ein Menuepunkt mit lauter Nullen waere
schlechter als keiner.

Zwei Stellen, an denen die App bewusst zurueckhaltend ist: ein unbekannter
Pruefpunkt erscheint als hohler Ring statt als geratenes Gruen, und der
Reifen-Kilometerstand wird nie zurueckgeschrieben, weil ihn der Server
fortschreibt.

49 Tests, darunter ein Rendertest ueber alle 21 Seiten in beiden Layouts
mit Beispieldaten in der Form, die das Backend wirklich liefert.
2026-08-11 11:17:47 +02:00

158 lines
5.4 KiB
TypeScript

/**
* Reifensätze. Vorlage: `vReifen()` im alten Panel.
*
* Der Kilometerstand je Satz kommt vom Backend (`reifenzaehler.py`) und wird
* hier nie zurückgeschrieben — sonst überschriebe ein im Browser gehaltener
* älterer Stand eine zwischenzeitliche Fortschreibung. Der Wechsel läuft
* deshalb auch über einen eigenen Dienst statt über das Profil.
*/
import { useState } from "react"
import { ActionButton, Feld, Seg, Tile } from "@audi-dash/ui"
import { useDaten } from "../daten/DatenKontext"
import type { Reifensatz } from "../daten/profilAdapter"
import { datum, de, deOderStrich } from "../format"
import { Wertzeile, Werteliste, bestaetigen } from "./bausteine"
import { Bild } from "./Bild"
import { kalenderDateiLaden } from "./kalender"
export function Reifen() {
const { fahrzeug, api, profilSpeichern } = useDaten()
const [laeuft, setzeLaeuft] = useState(false)
const [gezeigt, setzeGezeigt] = useState<"Sommer" | "Winter" | null>(null)
if (!fahrzeug) return null
const aktiv = fahrzeug.reifen.aktiv
const anzeige = gezeigt ?? aktiv
const satz: Reifensatz = anzeige === "Winter" ? fahrzeug.reifen.winter : fahrzeug.reifen.sommer
const wechsel = fahrzeug.reifen.wechsel as { datum?: string; vereinbart?: boolean }
const wechseln = async () => {
const ziel = aktiv === "Sommer" ? "winter" : "sommer"
if (!bestaetigen(`Auf ${ziel === "winter" ? "Winter" : "Sommer"}reifen umstellen?`)) return
setzeLaeuft(true)
try {
// Eigener Dienst: das Backend rechnet zuerst den alten Satz ab und
// schaltet erst dann um, damit keine Kilometer auf den neuen Satz
// rutschen, die noch auf dem alten gefahren wurden.
await api.rest.dienstAufrufen("pyscript", "audi_dashboard_reifen_wechseln", { satz: ziel })
} finally {
setzeLaeuft(false)
}
}
const wechseldatumSetzen = async (neuesDatum: string) => {
await profilSpeichern({
fahrzeug: {
...fahrzeug,
reifen: { ...fahrzeug.reifen, wechsel: { ...wechsel, datum: neuesDatum } },
},
})
}
return (
<>
<Tile>
<span className="ads-eyebrow">Montiert</span>
<div className="dm-detail__kopf">
<div className="dm-detail__zahl">{aktiv}</div>
<ActionButton onClick={() => void wechseln()} disabled={laeuft}>
{laeuft ? "Stelle um …" : `Auf ${aktiv === "Sommer" ? "Winter" : "Sommer"} wechseln`}
</ActionButton>
</div>
<Werteliste
kinder={
<>
<Wertzeile
label="Anzugsmoment der Radschrauben"
wert={
fahrzeug.reifen.anzugsmoment != null
? `${de(fahrzeug.reifen.anzugsmoment)} Nm`
: "—"
}
/>
<Wertzeile
label="Nächster Wechseltermin"
wert={wechsel.datum ? datum(wechsel.datum) : "—"}
/>
</>
}
/>
<Feld label="Wechseltermin" last>
<input
className="dm-eingabe"
type="date"
value={wechsel.datum ?? ""}
onChange={(e) => void wechseldatumSetzen(e.target.value)}
/>
</Feld>
{wechsel.datum && (
<div style={{ marginTop: 14 }}>
<ActionButton
onClick={() =>
kalenderDateiLaden(
"Reifenwechsel",
wechsel.datum!,
`Wechsel auf ${aktiv === "Sommer" ? "Winter" : "Sommer"}reifen`,
)
}
>
In den Kalender eintragen
</ActionButton>
</div>
)}
</Tile>
<Seg
options={[
{ value: "Sommer", label: "Sommer" },
{ value: "Winter", label: "Winter" },
]}
value={anzeige}
onChange={(wert) => setzeGezeigt(wert as "Sommer" | "Winter")}
/>
<Tile>
<span className="ads-eyebrow">
{anzeige}reifen {anzeige === aktiv ? "· montiert" : "· eingelagert"}
</span>
<Bild
datei={anzeige === "Winter" ? "rad-winter.webp" : "rad-sommer.webp"}
beschreibung={`${anzeige}rad`}
name={`${anzeige}rad`}
/>
<Werteliste
kinder={
<>
<Wertzeile label="Gefahrene Strecke" wert={`${deOderStrich(satz["km"] as number)} km`} />
<Wertzeile label="Größe" wert={(satz["mass"] as string) || "—"} />
<Wertzeile
label="Marke und Modell"
wert={
[satz["marke"], satz["modell"]].filter(Boolean).join(" ") || "—"
}
/>
<Wertzeile label="DOT" wert={(satz["dot"] as string) || "—"} />
<Wertzeile
label="Luftdruck"
wert={`${(satz["druck_vorne"] as string) || "—"} / ${(satz["druck_hinten"] as string) || "—"} bar`}
zusatz="vorne / hinten"
/>
{Boolean(satz["kommentar"]) && (
<Wertzeile label="Notiz" wert={satz["kommentar"] as string} />
)}
</>
}
/>
<p className="dm-fussnote">
Die gefahrene Strecke schreibt der Server bei jeder Änderung des Kilometerstands fort und
rechnet sie immer dem gerade montierten Satz zu.
</p>
</Tile>
</>
)
}