8e6e5bc2ee
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.
158 lines
5.4 KiB
TypeScript
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>
|
|
</>
|
|
)
|
|
}
|