Vollständiger Panel-companion-app-Paritätsaudit (11 Kapitel)
Systematischer Bildvergleich der companion-app gegen das HA-Panel (companion-app/AGENTS.md Kapitel 1-11): Farbtoken/Radien aus dem falschen Stylesheet gelesen (iOS-Overlay statt Basis-CSS, betraf fast jede Kachel/Farbe/Radius app-weit), vier app-weite @audi-dash/ui-Bugs (Switch/Seg/Feld rot statt neutral bzw. falsche Feldbreite), Reifen-Seite strukturell neu gebaut (beide Radsätze gleichzeitig statt Umschalter, editierbare Felder, Anzugsmoment-/km-Korrektur), Standort-Feature komplett neu (fehlte bisher ganz), sowie diverse Struktur-/Typografie-/Datenlücken in MeinAudi/Service/Versicherung/Sicherheit/Fahrten/Tanken/Statistik/Batterie/Einstellungen. Manifest auf 2026.8.30.2 angehoben, OTA-Bündel neu gebaut und in audi_ha_test verifiziert (sauberer Neustart, keine Tracebacks). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -9,12 +9,13 @@
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
|
||||
import { ActionButton, Pill, Tile } from "@audi-dash/ui"
|
||||
import { ActionButton, Feld, Pill, 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 { Leerzustand, Wertzeile, Werteliste } from "./bausteine"
|
||||
import { KRAFTSTOFFSORTEN, Leerzustand, Wertzeile, Werteliste } from "./bausteine"
|
||||
|
||||
interface BelegErgebnis {
|
||||
ok?: boolean
|
||||
@@ -27,6 +28,7 @@ interface BelegErgebnis {
|
||||
export function TankDetail({ id }: { id: string | undefined }) {
|
||||
const { tankvorgaenge, api } = useDaten()
|
||||
const tank = tankvorgaenge.find((t) => t.tank_id === id)
|
||||
const [bearbeitenOffen, setzeBearbeitenOffen] = useState(false)
|
||||
|
||||
if (!tank) {
|
||||
return (
|
||||
@@ -37,12 +39,21 @@ export function TankDetail({ id }: { id: string | undefined }) {
|
||||
)
|
||||
}
|
||||
|
||||
if (bearbeitenOffen) {
|
||||
return <TankBearbeiten tank={tank} beiFertig={() => setzeBearbeitenOffen(false)} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">{datumZeit(tank.ts)}</span>
|
||||
<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">
|
||||
<div className="dm-detail__zahl dm-detail__zahl--gross">
|
||||
{deOderStrich(tank.liters, 2)}
|
||||
<span className="dm-detail__einheit">l</span>
|
||||
</div>
|
||||
@@ -92,6 +103,169 @@ export function TankDetail({ id }: { id: string | undefined }) {
|
||||
)
|
||||
}
|
||||
|
||||
/** "YYYY-MM-DDTHH:mm" in Ortszeit für ein <input type="datetime-local">. */
|
||||
function alsDatetimeLocal(iso: string): string {
|
||||
const d = new Date(iso)
|
||||
if (Number.isNaN(d.getTime())) return ""
|
||||
const versatz = d.getTimezoneOffset() * 60_000
|
||||
return new Date(d.getTime() - versatz).toISOString().slice(0, 16)
|
||||
}
|
||||
|
||||
/** Bearbeiten-Formular für einen bestehenden Tankvorgang - dieselben Felder
|
||||
wie tankFelder() im Panel (Zeitpunkt, Kilometerstand, Distanz, Kraftstoff,
|
||||
Liter, Kosten, SmartDeal-Ersparnis, Tankstelle). Fehlte hier bisher
|
||||
vollständig - der einzige Weg, einen Wert zu korrigieren, war ein neuer
|
||||
Beleg-Upload. */
|
||||
function TankBearbeiten({ tank, beiFertig }: { tank: Tankvorgang; beiFertig: () => void }) {
|
||||
const { api, fahrzeug } = useDaten()
|
||||
const [zeitpunkt, setzeZeitpunkt] = useState(alsDatetimeLocal(tank.ts))
|
||||
const [km, setzeKm] = useState(tank.odometer_km != null ? String(tank.odometer_km) : "")
|
||||
const [distanz, setzeDistanz] = useState(tank.distance_km != null ? String(tank.distance_km) : "")
|
||||
const [kraftstoff, setzeKraftstoff] = useState(tank.fuel_type ?? KRAFTSTOFFSORTEN[0])
|
||||
const [liter, setzeLiter] = useState(tank.liters != null ? String(tank.liters) : "")
|
||||
const [kosten, setzeKosten] = useState(tank.fuel_total_eur != null ? String(tank.fuel_total_eur) : "")
|
||||
const [ersparnis, setzeErsparnis] = useState(tank.discount != null ? String(tank.discount) : "")
|
||||
const [station, setzeStation] = useState(tank.station_name ?? "")
|
||||
const [fehler, setzeFehler] = useState<string | null>(null)
|
||||
const [laeuft, setzeLaeuft] = useState(false)
|
||||
|
||||
const smartdealAktiv = fahrzeug?.smartdeal.aktiv === true
|
||||
|
||||
const speichern = async () => {
|
||||
setzeFehler(null)
|
||||
const literZahl = Number(liter.replace(",", "."))
|
||||
const kostenZahl = Number(kosten.replace(",", "."))
|
||||
if (!Number.isFinite(literZahl) || literZahl <= 0) {
|
||||
setzeFehler("Bitte die getankte Menge in Litern angeben.")
|
||||
return
|
||||
}
|
||||
if (!Number.isFinite(kostenZahl) || kostenZahl <= 0) {
|
||||
setzeFehler("Bitte den Gesamtbetrag angeben.")
|
||||
return
|
||||
}
|
||||
setzeLaeuft(true)
|
||||
try {
|
||||
const kmZahl = Number(km.replace(",", "."))
|
||||
const distanzZahl = Number(distanz.replace(",", "."))
|
||||
const ersparnisZahl = Number(ersparnis.replace(",", "."))
|
||||
await api.tankvorgangAktualisieren(tank.tank_id, {
|
||||
ts: zeitpunkt ? new Date(zeitpunkt).toISOString() : tank.ts,
|
||||
liter: literZahl,
|
||||
kosten: kostenZahl,
|
||||
kraftstoff,
|
||||
station: station || null,
|
||||
km: Number.isFinite(kmZahl) && km ? kmZahl : null,
|
||||
distanz: Number.isFinite(distanzZahl) && distanz ? distanzZahl : null,
|
||||
...(smartdealAktiv
|
||||
? { ersparnis: Number.isFinite(ersparnisZahl) && ersparnis ? ersparnisZahl : null }
|
||||
: {}),
|
||||
})
|
||||
beiFertig()
|
||||
} catch (ursache) {
|
||||
setzeFehler(ursache instanceof Error ? ursache.message : String(ursache))
|
||||
} finally {
|
||||
setzeLaeuft(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Tile>
|
||||
<div className="dm-abschnitt" style={{ marginBottom: 0 }}>
|
||||
<span className="ads-eyebrow">Tankvorgang bearbeiten</span>
|
||||
<button type="button" className="dm-textknopf" onClick={beiFertig} disabled={laeuft}>
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
<Feld label="Zeitpunkt">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
type="datetime-local"
|
||||
value={zeitpunkt}
|
||||
onChange={(e) => setzeZeitpunkt(e.target.value)}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Kilometerstand" unit="km">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
inputMode="numeric"
|
||||
placeholder="wird ergänzt"
|
||||
value={km}
|
||||
onChange={(e) => setzeKm(e.target.value)}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Gefahrene Distanz" unit="km">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
inputMode="decimal"
|
||||
placeholder="aus km-Stand"
|
||||
value={distanz}
|
||||
onChange={(e) => setzeDistanz(e.target.value)}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Kraftstoff">
|
||||
<select
|
||||
className="dm-auswahl"
|
||||
value={kraftstoff}
|
||||
onChange={(e) => setzeKraftstoff(e.target.value)}
|
||||
>
|
||||
{KRAFTSTOFFSORTEN.map((sorte) => (
|
||||
<option key={sorte} value={sorte}>
|
||||
{sorte}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Feld>
|
||||
<Feld label="Getankte Liter" unit="l">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
inputMode="decimal"
|
||||
placeholder="0,00"
|
||||
value={liter}
|
||||
onChange={(e) => setzeLiter(e.target.value)}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Kosten (real, nach Rabatt)" unit="€">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
inputMode="decimal"
|
||||
placeholder="0,00"
|
||||
value={kosten}
|
||||
onChange={(e) => setzeKosten(e.target.value)}
|
||||
/>
|
||||
</Feld>
|
||||
{smartdealAktiv && (
|
||||
<Feld label="SmartDeal-Ersparnis" unit="€">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
inputMode="decimal"
|
||||
placeholder="0,00"
|
||||
value={ersparnis}
|
||||
onChange={(e) => setzeErsparnis(e.target.value)}
|
||||
/>
|
||||
</Feld>
|
||||
)}
|
||||
<Feld label="Tankstelle" last>
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
placeholder="Name"
|
||||
value={station}
|
||||
onChange={(e) => setzeStation(e.target.value)}
|
||||
/>
|
||||
</Feld>
|
||||
{fehler && (
|
||||
<p className="dm-fehler" role="alert">
|
||||
{fehler}
|
||||
</p>
|
||||
)}
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<ActionButton onClick={() => void speichern()} disabled={laeuft}>
|
||||
{laeuft ? "Speichere …" : "Speichern"}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</Tile>
|
||||
)
|
||||
}
|
||||
|
||||
function BelegBereich({
|
||||
tankId,
|
||||
belegDatei,
|
||||
|
||||
Reference in New Issue
Block a user