Batteriespannungs-Messwertliste, Wisch-Löschen, Reifensatz-Archiv, CI-Icons
Fünf vom Nutzer angeforderte Erweiterungen in einer Runde:
- Batteriespannungs-Kachel bekommt eine Messwertliste (Datum, Uhrzeit,
Spannung, optional Außentemperatur über den neuen AUSSENTEMP_SENSOR),
erreichbar über ein neues list-s-Symbol in der Diagramm-Kachel. Werte
über AGM_RUHE_MAX_V (12,8 V) sind keine Ruhespannung, sondern
Lichtmaschinenspannung - die Zeile markiert das jetzt mit
"Generatorspannung" statt es unkommentiert als Messwert auszugeben.
- Jede Zeile der neuen Liste wischbar zum Löschen (neuer Dienst
batterieverlauf_loeschen), über dieselbe Wisch-Mechanik wie Fahrten/
Tankvorgänge in beiden Oberflächen.
- Batteriespannungs-Diagramm im Panel war auf großen Bildschirmen (≥860px,
Spaltenlayout) stark in die Breite gezogen (preserveAspectRatio="none"
bei fester Höhe) - gedeckelt wie die dort bereits vorhandenen Popups.
- Statistik-Tab-Symbol in beiden Oberflächen durch das Audi-CI-Symbol
polls-s ersetzt.
- Neue Möglichkeit, einen Reifensatz zu archivieren ("Neue Räder
anlegen"): der aktuelle Stand (km, Marke, Modell, DOT, Maße, Solldruck,
Kommentar) wandert in ein Archiv, der laufende Satz beginnt bei 0 km neu.
Archiv als aufklappbare Übersicht mit voller Bearbeitungsmöglichkeit,
über drei neue, bewusst nicht über profilSchreiben laufende Dienste
(reifen_archivieren/-aktualisieren/-loeschen) - aus demselben Grund wie
reifen_wechseln: kein im Browser gehaltener Stand darf einen
zwischenzeitlich fortgeschriebenen km-Wert überschreiben.
Live im Testcontainer geprüft (erstmals über :18123 statt :8123 erreichbar)
und dabei zwei echte Layout-Fehler gefunden, die kein Compile-/Testlauf
sehen konnte: die Archiv-Eingabefelder waren durch ein fälschlich
verwendetes .mitEinheit (feste 96px-Breite, eigentlich für Zahl+Einheit
gedacht) abgeschnitten ("Continenta" statt "Continental"), und die
Kilometerzahl in der eingeklappten Archiv-Zeile war klein an das Datum
gequetscht statt wie der Satzname lesbar. Beides behoben, live erneut
bestätigt.
Backend: py_compile clean, services.yaml ergänzt. Panel: node --check
clean. companion-app: tsc/Testsuite (146/146)/Build/OTA-Bündel alle grün.
Manifest 2026.8.25.2 → .8, jeder Neustart im Testcontainer sauber.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -44,6 +44,20 @@ export interface TankvorgangFelder {
|
||||
fahrt_manuell_anlegen/fahrt_aktualisieren).
|
||||
Alles außer den Zeitpunkten ist optional: leer bleibt es liegen, bis das
|
||||
Kilometerstand-Screening (§7.2) oder eine spätere Bearbeitung es füllt. */
|
||||
/** Eingabefelder für einen archivierten Reifensatz - Namen wie beim Backend
|
||||
(reifen.py: reifen_archiv_aktualisieren). */
|
||||
export interface ReifenArchivFelder {
|
||||
ersetzt_am?: string;
|
||||
km?: number | null;
|
||||
marke?: string | null;
|
||||
modell?: string | null;
|
||||
dot?: string | null;
|
||||
mass?: string | null;
|
||||
druck_vorne?: string | null;
|
||||
druck_hinten?: string | null;
|
||||
kommentar?: string | null;
|
||||
}
|
||||
|
||||
export interface FahrtFelder {
|
||||
ts_start?: string;
|
||||
ts_end?: string;
|
||||
@@ -174,6 +188,45 @@ export class DataMetricApi {
|
||||
);
|
||||
}
|
||||
|
||||
/** Löscht einen Tageseintrag aus der Batteriespannungs-Messwertliste. */
|
||||
batterieverlaufEintragLoeschen(datum: string): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
DIENST_DOMAIN,
|
||||
"batterieverlauf_loeschen",
|
||||
{ datum },
|
||||
"Messwert löschen",
|
||||
);
|
||||
}
|
||||
|
||||
/** "Neue Räder anlegen": archiviert den aktuellen Stand von `satz` und legt
|
||||
einen frischen (km 0) an. */
|
||||
reifenArchivieren(satz: "sommer" | "winter"): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
DIENST_DOMAIN,
|
||||
"reifen_archivieren",
|
||||
{ satz },
|
||||
"Neue Räder anlegen",
|
||||
);
|
||||
}
|
||||
|
||||
reifenArchivAktualisieren(id: string, felder: ReifenArchivFelder): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
DIENST_DOMAIN,
|
||||
"reifen_archiv_aktualisieren",
|
||||
{ id, ...felder },
|
||||
"Archivierten Reifensatz ändern",
|
||||
);
|
||||
}
|
||||
|
||||
reifenArchivLoeschen(id: string): Promise<unknown> {
|
||||
return this.warteschlange.einreihen(
|
||||
DIENST_DOMAIN,
|
||||
"reifen_archiv_loeschen",
|
||||
{ id },
|
||||
"Archivierten Reifensatz löschen",
|
||||
);
|
||||
}
|
||||
|
||||
/** Stößt eine sofortige Aktualisierung im Backend an (Pull-to-refresh). */
|
||||
jetztAktualisieren(): Promise<unknown> {
|
||||
return this.rest.dienstAufrufen(DIENST_DOMAIN, "jetzt_aktualisieren");
|
||||
|
||||
@@ -36,6 +36,22 @@ export interface Reifensatz {
|
||||
[weitere: string]: unknown
|
||||
}
|
||||
|
||||
/** Ein per "Neue Räder anlegen" archivierter Reifensatz (reifen.py
|
||||
archivieren()) - der vollständige Stand zum Zeitpunkt des Austauschs. */
|
||||
export interface ReifenArchivEintrag {
|
||||
id: string
|
||||
satz: "sommer" | "winter"
|
||||
ersetzt_am: string
|
||||
km: number | null
|
||||
marke: string | null
|
||||
modell: string | null
|
||||
dot: string | null
|
||||
mass: string | null
|
||||
druck_vorne: string | null
|
||||
druck_hinten: string | null
|
||||
kommentar: string | null
|
||||
}
|
||||
|
||||
export interface Fahrzeug {
|
||||
fin: string
|
||||
details: string
|
||||
@@ -74,6 +90,8 @@ export interface Fahrzeug {
|
||||
wechsel: Record<string, unknown>
|
||||
sommer: Reifensatz
|
||||
winter: Reifensatz
|
||||
/** Neueste zuerst - schon so im Profil (reifen.py archivieren()). */
|
||||
archiv: ReifenArchivEintrag[]
|
||||
}
|
||||
service: Record<string, unknown>
|
||||
technik: Record<string, unknown>
|
||||
@@ -179,6 +197,7 @@ export function profilZuFahrzeug(profil: Profil, status: Fahrzeugstatus): Fahrze
|
||||
wechsel: (reifen["wechsel"] ?? {}) as Record<string, unknown>,
|
||||
sommer: saetze["sommer"] ?? {},
|
||||
winter: saetze["winter"] ?? {},
|
||||
archiv: (reifen["archiv"] as ReifenArchivEintrag[] | undefined) ?? [],
|
||||
},
|
||||
service: abschnitt(profil, "service"),
|
||||
technik: abschnitt(profil, "technik"),
|
||||
|
||||
@@ -37,7 +37,7 @@ function fahrzeug(odo: number, modus = "hersteller"): Fahrzeug {
|
||||
oel: { modus, km: 15000, monate: 12, herstellerKm: 30000, herstellerMonate: 24 },
|
||||
versicherung: {},
|
||||
steuer: { faellig: "" },
|
||||
reifen: { aktiv: "Sommer", anzugsmoment: null, wechsel: {}, sommer: {}, winter: {} },
|
||||
reifen: { aktiv: "Sommer", anzugsmoment: null, wechsel: {}, sommer: {}, winter: {}, archiv: [] },
|
||||
service: {},
|
||||
technik: {},
|
||||
ausstattung: [],
|
||||
|
||||
@@ -17,6 +17,7 @@ export type SeitenName =
|
||||
// Unterseiten von "Mein Audi"
|
||||
| "ident"
|
||||
| "battverlauf"
|
||||
| "battliste"
|
||||
| "service"
|
||||
| "werkstatt"
|
||||
| "sbuch"
|
||||
@@ -54,6 +55,7 @@ export const ZURUECK: Partial<Record<SeitenName, SeitenName>> = {
|
||||
reifen: "audi",
|
||||
ident: "audi",
|
||||
battverlauf: "audi",
|
||||
battliste: "battverlauf",
|
||||
vers: "audi",
|
||||
beitrag: "vers",
|
||||
vertrag: "vers",
|
||||
@@ -78,6 +80,7 @@ export const TITEL: Record<SeitenName, string> = {
|
||||
fuel: "Tanken",
|
||||
ident: "Fahrzeugdaten",
|
||||
battverlauf: "Batterie",
|
||||
battliste: "Messwerte",
|
||||
service: "Service",
|
||||
werkstatt: "Werkstatt",
|
||||
sbuch: "Servicebuch",
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import { Tile } from "@audi-dash/ui"
|
||||
import { IconButton, Tile } from "@audi-dash/ui"
|
||||
|
||||
import { ENTITAETEN } from "../api"
|
||||
import { useDaten } from "../daten/DatenKontext"
|
||||
import { datum, de } from "../format"
|
||||
import type { SeitenName } from "../navigation"
|
||||
import { SymbolListe } from "../symbole"
|
||||
import { Leerzustand, Wertzeile, Werteliste } from "./bausteine"
|
||||
|
||||
interface Tageswert {
|
||||
@@ -29,8 +31,10 @@ interface Tageswert {
|
||||
}
|
||||
|
||||
/** Ab hier gilt eine Messung als nicht mehr in Ruhe aufgenommen - alles
|
||||
darüber ist schon Lichtmaschinenspannung, nicht mehr Ruhespannung. */
|
||||
const AGM_RUHE_MAX_V = 12.8
|
||||
darüber ist schon Lichtmaschinenspannung, nicht mehr Ruhespannung. Auch
|
||||
von BatterieListe.tsx verwendet, für die "Generatorspannung"-Kennzeichnung
|
||||
dort - eine einzige Schwelle für beide Ansichten. */
|
||||
export const AGM_RUHE_MAX_V = 12.8
|
||||
|
||||
/** Spannung → Ladezustand für eine AGM-Batterie in Ruhe. */
|
||||
const LADEKURVE: [number, number][] = [
|
||||
@@ -49,7 +53,7 @@ function ladezustand(spannung: number): number | null {
|
||||
return 0
|
||||
}
|
||||
|
||||
export function Batterie() {
|
||||
export function Batterie({ geheZu }: { geheZu: (name: SeitenName) => void }) {
|
||||
const { api, fahrzeug } = useDaten()
|
||||
const [verlauf, setzeVerlauf] = useState<Tageswert[] | null>(null)
|
||||
const [fehler, setzeFehler] = useState(false)
|
||||
@@ -97,7 +101,12 @@ export function Batterie() {
|
||||
return (
|
||||
<>
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Ruhespannung</span>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<span className="ads-eyebrow">Ruhespannung</span>
|
||||
<IconButton aria-label="Messwertliste" onClick={() => geheZu("battliste")}>
|
||||
<SymbolListe groesse={18} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="dm-detail__kopf">
|
||||
<div className="dm-detail__zahl">
|
||||
{letzter.min != null ? de(letzter.min, 2) : "—"}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Batteriespannungs-Messwertliste — ein Eintrag pro Tag (dieselben Daten wie
|
||||
* das Diagramm in Batterie.tsx), aber als Liste: Datum, Uhrzeit, Spannung,
|
||||
* Außentemperatur. Erreichbar über das Listen-Symbol oben rechts im
|
||||
* Diagramm.
|
||||
*
|
||||
* Jede Zeile lässt sich wie Fahrten/Tankvorgänge nach links wegwischen, um
|
||||
* sie zu löschen (Zeilenmenue - Wischgeste plus sichtbares Menü, siehe dort).
|
||||
*
|
||||
* Liegt der Tagesminimalwert über AGM_RUHE_MAX_V (Batterie.tsx), wurde er nie
|
||||
* in Ruhe gemessen (das Fahrzeug stand an diesem Tag nie lange genug still) -
|
||||
* die Zeile zeigt dann "Generatorspannung" statt so zu tun, als sei es ein
|
||||
* Ruhewert.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
import { Pill, Tile } from "@audi-dash/ui"
|
||||
|
||||
import { ENTITAETEN } from "../api"
|
||||
import { useDaten } from "../daten/DatenKontext"
|
||||
import { datum, datumZeit, de } from "../format"
|
||||
import { Leerzustand } from "./bausteine"
|
||||
import { AGM_RUHE_MAX_V } from "./Batterie"
|
||||
import { Zeilenmenue } from "./Zeilenmenue"
|
||||
|
||||
interface Tageswert {
|
||||
datum: string
|
||||
min: number | null
|
||||
max: number | null
|
||||
min_ts?: string
|
||||
min_temp_c?: number | null
|
||||
}
|
||||
|
||||
export function BatterieListe() {
|
||||
const { api } = useDaten()
|
||||
const [verlauf, setzeVerlauf] = useState<Tageswert[] | null>(null)
|
||||
const [fehler, setzeFehler] = useState(false)
|
||||
|
||||
const neuLaden = () => {
|
||||
void api.rest
|
||||
.datenLesen<Tageswert[]>(ENTITAETEN.batterieverlauf)
|
||||
.then((daten) => setzeVerlauf(Array.isArray(daten) ? daten : []))
|
||||
.catch(() => setzeFehler(true))
|
||||
}
|
||||
|
||||
useEffect(neuLaden, [api])
|
||||
|
||||
if (fehler) {
|
||||
return (
|
||||
<Leerzustand
|
||||
titel="Liste nicht abrufbar"
|
||||
text="Der Server hat die Messwertliste nicht geliefert. Beim nächsten Aufruf klappt es vielleicht wieder."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (!verlauf) return <div className="dm-laden">Liste wird geladen …</div>
|
||||
|
||||
const messwerte = verlauf.filter((t) => t.min != null).slice().reverse()
|
||||
|
||||
if (messwerte.length === 0) {
|
||||
return (
|
||||
<Leerzustand
|
||||
titel="Keine Messwerte"
|
||||
text="Für dieses Fahrzeug liefert die Anbindung derzeit keinen Spannungssensor."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const loeschen = async (eintrag: Tageswert) => {
|
||||
await api.batterieverlaufEintragLoeschen(eintrag.datum)
|
||||
neuLaden()
|
||||
}
|
||||
|
||||
return (
|
||||
<Tile>
|
||||
{messwerte.map((t) => (
|
||||
<Zeilenmenue
|
||||
key={t.datum}
|
||||
loeschFrage={`Messwert vom ${datum(t.datum)} löschen?`}
|
||||
beiLoeschen={() => void loeschen(t)}
|
||||
kinder={
|
||||
<div className="dm-listenzeile">
|
||||
<span className="dm-listenzeile__haupt">
|
||||
<span className="dm-listenzeile__titel">
|
||||
{de(t.min!, 2)} V
|
||||
<span className="dm-listenzeile__neben">
|
||||
{t.min_temp_c != null ? `${de(t.min_temp_c, 1)} °C` : "—"}
|
||||
</span>
|
||||
</span>
|
||||
<span className="dm-listenzeile__unter">
|
||||
{t.min_ts ? datumZeit(t.min_ts) : datum(t.datum)}
|
||||
</span>
|
||||
</span>
|
||||
{t.min! > AGM_RUHE_MAX_V && <Pill>Generatorspannung</Pill>}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Tile>
|
||||
)
|
||||
}
|
||||
@@ -9,12 +9,13 @@
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
import { ActionButton, Feld, Seg, Tile } from "@audi-dash/ui"
|
||||
import { Accordion, ActionButton, Feld, IconButton, Seg, Tile } from "@audi-dash/ui"
|
||||
|
||||
import { DIENST_DOMAIN } from "../api"
|
||||
import { useDaten } from "../daten/DatenKontext"
|
||||
import type { Reifensatz } from "../daten/profilAdapter"
|
||||
import type { Reifensatz, ReifenArchivEintrag } from "../daten/profilAdapter"
|
||||
import { datum, de, deOderStrich } from "../format"
|
||||
import { SymbolPaket } from "../symbole"
|
||||
import { Wertzeile, Werteliste, bestaetigen } from "./bausteine"
|
||||
import { Bild } from "./Bild"
|
||||
import { kalenderDateiLaden } from "./kalender"
|
||||
@@ -45,6 +46,19 @@ export function Reifen() {
|
||||
}
|
||||
}
|
||||
|
||||
const archivieren = async () => {
|
||||
if (!bestaetigen("Neue Räder anlegen?")) return
|
||||
setzeLaeuft(true)
|
||||
try {
|
||||
// Eigener Dienst wie reifen_wechseln: archiviert den bisherigen Stand
|
||||
// und setzt den laufenden Satz zurück (km 0, Marke/Modell/DOT/Kommentar
|
||||
// leer - siehe reifen.py archivieren()).
|
||||
await api.reifenArchivieren(anzeige === "Winter" ? "winter" : "sommer")
|
||||
} finally {
|
||||
setzeLaeuft(false)
|
||||
}
|
||||
}
|
||||
|
||||
const wechseldatumSetzen = async (neuesDatum: string) => {
|
||||
await profilSpeichern({
|
||||
fahrzeug: {
|
||||
@@ -125,6 +139,11 @@ export function Reifen() {
|
||||
beschreibung={`${anzeige}rad`}
|
||||
name={`${anzeige}rad`}
|
||||
/>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end", marginTop: -6 }}>
|
||||
<IconButton aria-label="Neue Räder anlegen" onClick={() => void archivieren()} disabled={laeuft}>
|
||||
<SymbolPaket groesse={18} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<Werteliste
|
||||
kinder={
|
||||
<>
|
||||
@@ -153,6 +172,193 @@ export function Reifen() {
|
||||
rechnet sie immer dem gerade montierten Satz zu.
|
||||
</p>
|
||||
</Tile>
|
||||
|
||||
{fahrzeug.reifen.archiv.length > 0 && (
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Archiv</span>
|
||||
{fahrzeug.reifen.archiv.map((eintrag) => (
|
||||
<ArchivZeile key={eintrag.id} eintrag={eintrag} />
|
||||
))}
|
||||
</Tile>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ArchivZeile({ eintrag }: { eintrag: ReifenArchivEintrag }) {
|
||||
const { api } = useDaten()
|
||||
const [bearbeiten, setzeBearbeiten] = useState(false)
|
||||
const [laeuft, setzeLaeuft] = useState(false)
|
||||
const [form, setzeForm] = useState(() => ({
|
||||
ersetzt_am: eintrag.ersetzt_am,
|
||||
km: String(eintrag.km ?? ""),
|
||||
marke: eintrag.marke ?? "",
|
||||
modell: eintrag.modell ?? "",
|
||||
dot: eintrag.dot ?? "",
|
||||
mass: eintrag.mass ?? "",
|
||||
druck_vorne: eintrag.druck_vorne ?? "",
|
||||
druck_hinten: eintrag.druck_hinten ?? "",
|
||||
kommentar: eintrag.kommentar ?? "",
|
||||
}))
|
||||
|
||||
const speichern = async () => {
|
||||
setzeLaeuft(true)
|
||||
try {
|
||||
const kmZahl = Number(form.km.replace(",", "."))
|
||||
await api.reifenArchivAktualisieren(eintrag.id, {
|
||||
ersetzt_am: form.ersetzt_am,
|
||||
km: Number.isFinite(kmZahl) ? kmZahl : null,
|
||||
marke: form.marke || null,
|
||||
modell: form.modell || null,
|
||||
dot: form.dot || null,
|
||||
mass: form.mass || null,
|
||||
druck_vorne: form.druck_vorne || null,
|
||||
druck_hinten: form.druck_hinten || null,
|
||||
kommentar: form.kommentar || null,
|
||||
})
|
||||
setzeBearbeiten(false)
|
||||
} finally {
|
||||
setzeLaeuft(false)
|
||||
}
|
||||
}
|
||||
|
||||
const loeschen = async () => {
|
||||
if (!bestaetigen("Diesen archivierten Reifensatz löschen?")) return
|
||||
setzeLaeuft(true)
|
||||
try {
|
||||
await api.reifenArchivLoeschen(eintrag.id)
|
||||
} finally {
|
||||
setzeLaeuft(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
title={eintrag.satz === "winter" ? "Winterräder" : "Sommerräder"}
|
||||
summary={
|
||||
// km in derselben Größe/Farbe wie der Titel (nicht als angehängte
|
||||
// .ads-acc-h__v-Kleinschrift ans Datum gequetscht - live beobachtet,
|
||||
// dass das schwer lesbar war), Datum als eigene, kleinere Zeile
|
||||
// darunter. Parität zum Panel (dieselbe Umstellung dort).
|
||||
<span style={{ display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 3 }}>
|
||||
<span style={{ color: "var(--fg)", fontSize: 15 }}>{deOderStrich(eintrag.km)} km</span>
|
||||
<small>Ersetzt am {datum(eintrag.ersetzt_am)}</small>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{bearbeiten ? (
|
||||
<>
|
||||
<Feld label="Ersetzt am">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
type="date"
|
||||
value={form.ersetzt_am}
|
||||
onChange={(e) => setzeForm({ ...form, ersetzt_am: e.target.value })}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Gefahrene Strecke" unit="km">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
inputMode="decimal"
|
||||
value={form.km}
|
||||
onChange={(e) => setzeForm({ ...form, km: e.target.value })}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Marke">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
value={form.marke}
|
||||
onChange={(e) => setzeForm({ ...form, marke: e.target.value })}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Modell">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
value={form.modell}
|
||||
onChange={(e) => setzeForm({ ...form, modell: e.target.value })}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="DOT">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
value={form.dot}
|
||||
onChange={(e) => setzeForm({ ...form, dot: e.target.value })}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Größe">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
value={form.mass}
|
||||
onChange={(e) => setzeForm({ ...form, mass: e.target.value })}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Luftdruck vorne" unit="bar">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
value={form.druck_vorne}
|
||||
onChange={(e) => setzeForm({ ...form, druck_vorne: e.target.value })}
|
||||
/>
|
||||
</Feld>
|
||||
<Feld label="Luftdruck hinten" unit="bar">
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
value={form.druck_hinten}
|
||||
onChange={(e) => setzeForm({ ...form, druck_hinten: e.target.value })}
|
||||
/>
|
||||
</Feld>
|
||||
{/* Kein <Feld> hier (Label und Eingabe teilen sich sonst eine Zeile,
|
||||
die Eingabe bleibt auf ihrer intrinsischen Breite - für eine
|
||||
Notiz wie "Profiltiefe 5,1 mm bei der letzten Kontrolle" zu
|
||||
wenig Platz, live beobachtet). Eingabe über die volle Breite,
|
||||
Label darüber - Parität zum Panel (.feld--breit). */}
|
||||
<div className="ads-feld" style={{ display: "block", borderBottom: 0 }}>
|
||||
<label style={{ display: "block", marginBottom: 8 }}>Notiz</label>
|
||||
<input
|
||||
className="dm-eingabe"
|
||||
style={{ width: "100%", textAlign: "left" }}
|
||||
value={form.kommentar}
|
||||
onChange={(e) => setzeForm({ ...form, kommentar: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 10, marginTop: 14 }}>
|
||||
<ActionButton onClick={() => void speichern()} disabled={laeuft}>
|
||||
{laeuft ? "Speichere …" : "Speichern"}
|
||||
</ActionButton>
|
||||
<ActionButton onClick={() => setzeBearbeiten(false)} disabled={laeuft}>
|
||||
Abbrechen
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<ActionButton variant="destructive" onClick={() => void loeschen()} disabled={laeuft}>
|
||||
Löschen
|
||||
</ActionButton>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Werteliste
|
||||
kinder={
|
||||
<>
|
||||
<Wertzeile label="Größe" wert={eintrag.mass || "—"} />
|
||||
<Wertzeile
|
||||
label="Marke und Modell"
|
||||
wert={[eintrag.marke, eintrag.modell].filter(Boolean).join(" ") || "—"}
|
||||
/>
|
||||
<Wertzeile label="DOT" wert={eintrag.dot || "—"} />
|
||||
<Wertzeile
|
||||
label="Luftdruck"
|
||||
wert={`${eintrag.druck_vorne || "—"} / ${eintrag.druck_hinten || "—"} bar`}
|
||||
zusatz="vorne / hinten"
|
||||
/>
|
||||
{Boolean(eintrag.kommentar) && <Wertzeile label="Notiz" wert={eintrag.kommentar!} />}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<ActionButton onClick={() => setzeBearbeiten(true)}>Bearbeiten</ActionButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Accordion>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { useDaten } from "../daten/DatenKontext"
|
||||
import type { Route, SeitenName } from "../navigation"
|
||||
import { Batterie } from "./Batterie"
|
||||
import { BatterieListe } from "./BatterieListe"
|
||||
import { Einstellungen } from "./Einstellungen"
|
||||
import { FahrtDetail } from "./FahrtDetail"
|
||||
import { Fahrten } from "./Fahrten"
|
||||
@@ -69,7 +70,9 @@ export function SeiteFuer(props: SeitenProps) {
|
||||
case "ident":
|
||||
return <Fahrzeugdaten />
|
||||
case "battverlauf":
|
||||
return <Batterie />
|
||||
return <Batterie geheZu={geheZu} />
|
||||
case "battliste":
|
||||
return <BatterieListe />
|
||||
case "service":
|
||||
return <Service geheZu={geheZu} />
|
||||
case "werkstatt":
|
||||
|
||||
@@ -13,14 +13,22 @@ interface SymbolProps {
|
||||
groesse?: number | undefined
|
||||
}
|
||||
|
||||
function Rahmen({ groesse = 24, kinder }: SymbolProps & { kinder: ReactElement }) {
|
||||
/** `voll`: Fuellflaeche statt Kontur, fuer Symbole aus dem Audi-CI-Icon-Satz,
|
||||
deren Pfade ihr eigenes fill="currentColor" mitbringen - ohne stroke="none"
|
||||
hier wuerde der geerbte Kontur-Stroke trotzdem um jede Flaeche gezeichnet.
|
||||
Entspricht der .voll-Klasse im Panel (audi-dashboard-app.js). */
|
||||
function Rahmen({
|
||||
groesse = 24,
|
||||
voll = false,
|
||||
kinder,
|
||||
}: SymbolProps & { voll?: boolean; kinder: ReactElement }) {
|
||||
return (
|
||||
<svg
|
||||
width={groesse}
|
||||
height={groesse}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
fill={voll ? "currentColor" : "none"}
|
||||
stroke={voll ? "none" : "currentColor"}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -70,8 +78,42 @@ export function SymbolFahrten({ groesse }: SymbolProps) {
|
||||
)
|
||||
}
|
||||
|
||||
// CI-Symbol polls-s statt der vorherigen handgezeichneten Balken (Parität
|
||||
// zum Panel, siehe ICONS.stat dort).
|
||||
export function SymbolStatistik({ groesse }: SymbolProps) {
|
||||
return <Rahmen groesse={groesse} kinder={<path d="M4 20V9.5M10 20V4.5M16 20v-8M22 20H2" />} />
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
voll
|
||||
kinder={
|
||||
<path d="M9 0h6v21H9zm1 1v19h4V1zm7 4h6v16h-6zm1 1v14h4V6zM1 9h6v12H1zm1 1v10h4V10zM1 23h22v1H1z" />
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// CI-Symbol list-s: Sprung vom Diagramm zur Werteliste (Batteriespannung).
|
||||
export function SymbolListe({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
voll
|
||||
kinder={<path d="M5 8H3V7h2zm2-1h14v1H7zm-4 4h2v1H3zm4 0h9v1H7zm-4 4h2v1H3zm4 0h5v1H7z" />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// CI-Symbol package-s: "Neue Räder anlegen" (Reifensatz archivieren).
|
||||
export function SymbolPaket({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
voll
|
||||
kinder={
|
||||
<path d="m12 .96 11 4.71v12.66l-11 4.71-11-4.71V5.67zM2 6.76v10.91l9.5 4.07V10.83zm10.5 4.07v10.91l9.5-4.07V6.76zM21.23 6 12 2.04 8.41 3.58l9.2 3.97zM7.15 4.12 2.77 6 12 9.96l4.34-1.86-9.2-3.98Z" />
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolTanken({ groesse }: SymbolProps) {
|
||||
|
||||
Reference in New Issue
Block a user