4dbf3cb173
Erste drei Screens auf der fertigen Datenschicht. Uebersicht mit Fahrzeugbild, Sicherungsstatus, Reichweite und Tankfuellstand, Kilometerstand samt Service-Faelligkeit und Vorschau auf letzte Fahrt und Tankung. Fahrzeugstatus zeigt die 16 Einzelpruefungen, wobei ein unbekannter Sensor als hohler Ring erscheint statt als gruen geratener Punkt - dieselbe Vorsicht, die schon das Backend walten laesst. Mein Audi buendelt Fotogalerie und Wege zu den Unterseiten. Dazu die geteilte Rechenlogik der Statistik portiert (Zeitraeume, Langzeitverbrauch mit Plausibilitaetsfenster, Gruppierung nach Jahr und Monat) und Formatierung im deutschen Format. Bild-Komponente kapselt eine Reibung zwischen App und Bibliothek: die App laeuft mit exactOptionalPropertyTypes, ein undefined an einem optionalen Prop waere sonst an jeder Aufrufstelle einzeln zu behandeln.
87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from "react"
|
|
|
|
import { DataMetricApi, zugangLesen } from "./api"
|
|
import { DatenAnbieter, useDaten } from "./daten/DatenKontext"
|
|
import { Shell } from "./Shell"
|
|
import type { Route, SeitenName } from "./navigation"
|
|
import { useTabBeschriftung, useTheme } from "./theme"
|
|
import { Einrichtung } from "./screens/Einrichtung"
|
|
import { Hinweisleiste } from "./screens/Hinweisleiste"
|
|
import { SeiteFuer } from "./screens/register"
|
|
import { zugangMerken } from "./screens/zugang"
|
|
|
|
import "./stile/rahmen.css"
|
|
import "./stile/screens.css"
|
|
|
|
type Startzustand = "pruefen" | "einrichten" | "bereit"
|
|
|
|
/**
|
|
* Wurzel der App. Entscheidet zuerst, ob Zugangsdaten vorliegen — ohne sie
|
|
* gibt es nichts anzuzeigen außer der Ersteinrichtung. Danach hängt alles
|
|
* unter dem Datenkontext.
|
|
*/
|
|
export function App() {
|
|
const [theme] = useTheme()
|
|
const [start, setzeStart] = useState<Startzustand>("pruefen")
|
|
|
|
// Genau eine Instanz für die Lebensdauer der App: sie hält die
|
|
// WebSocket-Verbindung und die Warteschlange.
|
|
const api = useMemo(() => new DataMetricApi(), [])
|
|
|
|
useEffect(() => {
|
|
void zugangLesen().then(async (zugang) => {
|
|
// Adresse für Bild-URLs merken, bevor der erste Screen rendert.
|
|
if (zugang) await zugangMerken()
|
|
setzeStart(zugang ? "bereit" : "einrichten")
|
|
})
|
|
}, [])
|
|
|
|
return (
|
|
<div className="ads-root dm-wurzel" data-theme={theme}>
|
|
{start === "pruefen" && <div className="dm-laden">Einen Moment …</div>}
|
|
{start === "einrichten" && <Einrichtung fertig={() => setzeStart("bereit")} />}
|
|
{start === "bereit" && (
|
|
<DatenAnbieter api={api}>
|
|
<AngemeldeteApp beiAbmeldung={() => setzeStart("einrichten")} />
|
|
</DatenAnbieter>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function AngemeldeteApp({ beiAbmeldung }: { beiAbmeldung: () => void }) {
|
|
const [route, setzeRoute] = useState<Route>({ name: "home" })
|
|
const [tabBeschriftung, setzeTabBeschriftung] = useTabBeschriftung()
|
|
const daten = useDaten()
|
|
|
|
const geheZu = useCallback((name: SeitenName, id?: string) => {
|
|
setzeRoute(id === undefined ? { name } : { name, id })
|
|
document.querySelector(".dm-inhalt")?.scrollTo({ top: 0 })
|
|
}, [])
|
|
|
|
// Ein abgelaufener oder zurückgezogener Token führt zurück in die
|
|
// Ersteinrichtung statt in eine endlose Fehlerschleife.
|
|
useEffect(() => {
|
|
if (daten.verbindung === "nicht_angemeldet") beiAbmeldung()
|
|
}, [daten.verbindung, beiAbmeldung])
|
|
|
|
return (
|
|
<Shell
|
|
route={route}
|
|
geheZu={geheZu}
|
|
tabBeschriftung={tabBeschriftung}
|
|
hinweis={
|
|
<Hinweisleiste verbindung={daten.verbindung} warteschlange={daten.warteschlange} />
|
|
}
|
|
>
|
|
<SeiteFuer
|
|
route={route}
|
|
geheZu={geheZu}
|
|
tabBeschriftung={tabBeschriftung}
|
|
setzeTabBeschriftung={setzeTabBeschriftung}
|
|
beiAbmeldung={beiAbmeldung}
|
|
/>
|
|
</Shell>
|
|
)
|
|
}
|