Paritaetsrunden 2 und 3, Designpruefung umgesetzt (2026.8.30.8)

Drei zusammenhaengende Runden, alle live bei 375x812 gegen audi_ha_test
geprueft und in beiden Codebasen angewandt.

Paritaetsrunde 2: der gemeinsame Rahmen war das eigentliche Problem -
Seitenrand, Kachelabstaende, Kopfleiste, Tableiste und vier Bausteine des
Design-Systems trugen noch den Stand vor der iOS-Entscheidung. Dazu sieben
Bildschirme neu aufgebaut. Zwei Panel-Fehler dabei mitbehoben: teaser()
zeigte unter "Letzte Fahrt" den aeltesten Datensatz, und "Daten bearbeiten"
war ein toter Knopf.

Paritaetsrunde 3: das Panel hat nie Audi Type gerendert. @font-face in einem
Shadow Root wird ignoriert - Schriftschnitte registriert der Browser pro
Dokument, nie pro Shadow Tree. Die drei Schnitte liegen jetzt in
audi-dashboard-schriften.css und werden ins Dokument gehaengt. Damit erledigt
sich eine ganze Reihe von "die Schrift sieht anders aus"-Eindruecken: die
beiden Anwendungen zeigten tatsaechlich verschiedene Schriften.
Ausserdem "Daten bearbeiten" im Panel gebaut und portiert, das Zeilenmenue
entfernt und die letzten neun Bildschirme angeglichen.

Designpruefung: die fuenf Punkte der Reihenfolge. Beim vierten hat das Messen
den Befund veraendert - gezaehlt waren die deklarierten Groessen, wirksam war
laengst eine saubere Sieben-Schritt-Skala mit 27 Ausreissern; die sind jetzt
auf den naechsten Schritt gezogen, keiner verschiebt sich um mehr als 1px.

Zuletzt: die Standortvorschau zeichnete die falsche Nadel (das Panel wechselt
den Icon-Satz ab 34px, die App nahm immer den grossen), und der Kopfabstand
der App ist auf den sicheren Bereich reduziert - die 56px des Panels liegen
dort unter der Kopfleiste von Home Assistant, in der nativen Huelle steht
darueber nichts.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-30 18:31:46 +02:00
co-authored by Claude Opus 5
parent 25c0d073c4
commit 4a58021670
64 changed files with 4129 additions and 1431 deletions
+98 -77
View File
@@ -9,13 +9,16 @@
import { useEffect, useRef, useState } from "react"
import { ActionButton, Feld, Pill, Tile } from "@audi-dash/ui"
import { ActionButton, Feld, Fig, Tile } from "@audi-dash/ui"
import type { Tankvorgang } from "../api"
import { ENTITAETEN } from "../api"
import { useDaten } from "../daten/DatenKontext"
import { datum, datumZeit, de, deOderStrich, eur } from "../format"
import { KRAFTSTOFFSORTEN, Leerzustand, Wertzeile, Werteliste } from "./bausteine"
import { datum, de, deOderStrich, eur, uhrzeit } from "../format"
import type { SeitenName } from "../navigation"
import { KRAFTSTOFFSORTEN, Leerzustand, Wertzeile, Werteliste, bestaetigen } from "./bausteine"
import { markenlogoUrl } from "./bilder"
import { TankstellenKarte } from "./TankstellenKarte"
interface BelegErgebnis {
ok?: boolean
@@ -25,8 +28,8 @@ interface BelegErgebnis {
[weitere: string]: unknown
}
export function TankDetail({ id }: { id: string | undefined }) {
const { tankvorgaenge, api } = useDaten()
export function TankDetail({ id, geheZu }: { id: string | undefined; geheZu: (name: SeitenName) => void }) {
const { tankvorgaenge, fahrzeug, api } = useDaten()
const tank = tankvorgaenge.find((t) => t.tank_id === id)
const [bearbeitenOffen, setzeBearbeitenOffen] = useState(false)
@@ -39,66 +42,100 @@ export function TankDetail({ id }: { id: string | undefined }) {
)
}
const loeschen = () => {
if (!tank || !bestaetigen("Tankvorgang löschen?")) return
void api.tankvorgangLoeschen(tank.tank_id)
geheZu("fuel")
}
if (bearbeitenOffen) {
return <TankBearbeiten tank={tank} beiFertig={() => setzeBearbeitenOffen(false)} />
}
/* Aufbau 1:1 wie vFill(): Karte, randlose „Getankt"-Kachel mit der
52px-Zahl, dann eine einzige Wertezeilen-Kachel in der Reihenfolge des
Panels (Zeitpunkt/Kilometerstand/Gefahrene Distanz/Kraftstoff/Getankte
Liter/Kosten/Preis je Liter/SmartDeal-Ersparnis/Tankstelle) mit den drei
Knöpfen darunter.
Vorher: eigene Kopfzeile mit Zeitstempel und Bearbeiten-Textknopf, Pillen
für Status und Quelle (die das Panel hier nicht führt), andere Reihenfolge
und Bezeichnungen („Gesamtbetrag", „Seit der letzten Tankung",
„Listenpreis") und der Beleg in einer eigenen Kachel. */
const preisJeLiter =
tank.liters && tank.fuel_total_eur != null ? tank.fuel_total_eur / tank.liters : null
return (
<>
<Tile className="dm-kartenkachel">
<TankstellenKarte adresse={tank.station_address} />
</Tile>
<Tile variant="flat" className="dm-distanzkachel">
<span className="ads-eyebrow">Getankt</span>
<div style={{ marginTop: "10px" }}>
<Fig value={deOderStrich(tank.liters, 2)} unit="l" size={52} />
</div>
</Tile>
<Tile>
<div className="dm-abschnitt" style={{ marginBottom: 0 }}>
<span className="ads-eyebrow">{datumZeit(tank.ts)}</span>
<button type="button" className="dm-textknopf" onClick={() => setzeBearbeitenOffen(true)}>
Bearbeiten
</button>
</div>
<div className="dm-detail__kopf">
<div className="dm-detail__zahl dm-detail__zahl--gross">
{deOderStrich(tank.liters, 2)}
<span className="dm-detail__einheit">l</span>
</div>
<div className="dm-detail__marken">
{tank.status === "unvollständig" && <Pill>unvollständig</Pill>}
{tank.source === "beleg" && <Pill>aus Beleg</Pill>}
{tank.source === "manual" && <Pill>selbst eingetragen</Pill>}
{tank.source === "auto" && <Pill>automatisch erkannt</Pill>}
</div>
</div>
<Werteliste
kinder={
<>
<Wertzeile label="Zeitpunkt" wert={datum(tank.ts)} zusatz={`${uhrzeit(tank.ts)} Uhr`} />
<Wertzeile
label="Gesamtbetrag"
wert={tank.fuel_total_eur != null ? `${eur(tank.fuel_total_eur)} ` : ""}
label="Kilometerstand"
wert={tank.odometer_km != null ? `${de(tank.odometer_km)} km` : "unbekannt"}
/>
<Wertzeile
label="Gefahrene Distanz"
wert={tank.distance_km != null ? `${de(tank.distance_km)} km` : "unbekannt"}
zusatz="seit der letzten Tankung"
/>
<Wertzeile label="Kraftstoff" wert={tank.fuel_type ?? KRAFTSTOFFSORTEN[0]} />
<Wertzeile label="Getankte Liter" wert={deOderStrich(tank.liters, 2) + " l"} />
<Wertzeile
label="Kosten"
wert={tank.fuel_total_eur != null ? `${eur(tank.fuel_total_eur)}` : ""}
zusatz="real, nach Rabatt"
/>
<Wertzeile
label="Preis je Liter"
wert={tank.price_per_l != null ? `${eur(tank.price_per_l)}` : ""}
zusatz={
tank.list_price_per_l != null
? `Listenpreis ${eur(tank.list_price_per_l)}`
: undefined
}
wert={preisJeLiter != null ? `${de(preisJeLiter, 2)}` : ""}
zusatz="Kosten ÷ Liter"
/>
{tank.discount != null && tank.discount > 0 && (
<Wertzeile label="Ersparnis" wert={`${eur(tank.discount)}`} />
{fahrzeug?.smartdeal.aktiv && tank.discount != null && (
<Wertzeile
label={
<span className="dm-marke">
<img
src={markenlogoUrl()}
alt=""
onError={(e) => {
e.currentTarget.style.display = "none"
}}
/>
SmartDeal-Ersparnis
</span>
}
wert={`${eur(tank.discount)}`}
/>
)}
<Wertzeile label="Tankstelle" wert={tank.station_name ?? "—"} zusatz={tank.station_address ?? undefined} />
<Wertzeile label="Kraftstoff" wert={tank.product_name ?? tank.fuel_type ?? "—"} />
<Wertzeile
label="Kilometerstand"
wert={tank.odometer_km != null ? `${de(tank.odometer_km)} km` : "—"}
/>
<Wertzeile
label="Seit der letzten Tankung"
wert={tank.distance_km != null ? `${de(tank.distance_km)} km` : "—"}
label="Tankstelle"
wert={tank.station_name ?? "unbekannt"}
zusatz={tank.station_address ?? ""}
/>
</>
}
/>
<ActionButton onClick={() => setzeBearbeitenOffen(true)}>Werte bearbeiten</ActionButton>
<BelegKnopf tankId={tank.tank_id} belegDatei={tank.receipt_file ?? null} api={api} />
</Tile>
<BelegBereich tankId={tank.tank_id} belegDatei={tank.receipt_file ?? null} belegNr={tank.receipt_no ?? null} zeitpunkt={tank.ts} api={api} />
<ActionButton variant="destructive" onClick={() => void loeschen()}>
Tankvorgang löschen
</ActionButton>
</>
)
}
@@ -266,17 +303,17 @@ function TankBearbeiten({ tank, beiFertig }: { tank: Tankvorgang; beiFertig: ()
)
}
function BelegBereich({
/* Im Panel ist der Beleg kein eigener Abschnitt, sondern ein Knopf in der
Wertezeilen-Kachel ("Beleg ersetzen"/"Beleg nachtragen"). Die frühere
eigene Kachel mit Dateiname und Beleg-Nummer hatte dort keine
Entsprechung. */
function BelegKnopf({
tankId,
belegDatei,
belegNr,
zeitpunkt,
api,
}: {
tankId: string
belegDatei: string | null
belegNr: string | null
zeitpunkt: string
api: ReturnType<typeof useDaten>["api"]
}) {
const dateiwahl = useRef<HTMLInputElement | null>(null)
@@ -346,24 +383,7 @@ function BelegBereich({
}
return (
<Tile>
<span className="ads-eyebrow">Beleg</span>
{belegDatei ? (
<Werteliste
kinder={
<>
<Wertzeile label="Hinterlegt" wert={belegDatei} />
<Wertzeile label="Beleg-Nummer" wert={belegNr ?? "—"} />
</>
}
/>
) : (
<p className="dm-fussnote">
Für diese Tankung liegt noch kein Beleg vor. Aus einem Shell-Beleg liest die App Menge,
Betrag, Tankstelle und Ersparnis selbst aus.
</p>
)}
<>
<input
ref={dateiwahl}
type="file"
@@ -375,7 +395,6 @@ function BelegBereich({
e.target.value = ""
}}
/>
{meldung && (
<p
className={meldung.art === "fehler" ? "dm-fehler" : "dm-erfolg"}
@@ -384,17 +403,19 @@ function BelegBereich({
{meldung.text}
</p>
)}
<div style={{ marginTop: 14 }}>
<ActionButton onClick={() => dateiwahl.current?.click()} disabled={laeuft || wartet}>
{laeuft ? "Lade hoch …" : wartet ? "Beleg wird gelesen …" : belegDatei ? "Beleg ersetzen" : "Beleg hochladen"}
</ActionButton>
</div>
<p className="dm-fussnote">
Der Beleg muss vom {datum(zeitpunkt)} sein sonst weist der Server ihn ab, damit keine
fremde Tankung in diesen Eintrag gerät.
</p>
</Tile>
<ActionButton onClick={() => dateiwahl.current?.click()} disabled={laeuft || wartet}>
{laeuft
? "Lade hoch …"
: wartet
? "Beleg wird gelesen …"
: belegDatei
? "Beleg ersetzen"
: "Beleg nachtragen"}
</ActionButton>
{/* Der Datumshinweis darunter ist entfallen - im Panel steht an dieser
Stelle nur im Fehlerfall eine Meldung (belegFehler), kein
dauerhafter Erklärtext. */}
</>
)
}